0

I've made a function to turn a MM/DD/YYYY date into a DD-MMM-YYYY date (e.g. 05/03/2023 to 03-MAY-2023), but in the function I use date.split('/') twice and I'd prefer to only use it once.

I know I could use date = date.split('/') before the lambda function, I was just wondering if there is a way to do that inline with a lambda function?

# Current code
# Given vars
months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC']
date = "05/03/2023"
# Function
newDate = lambda date : '-'.join([date.split('/')[i] if i != 0 else months[int(date.split('/')[i])-1] for i in [1,0,2]])


# Function I want
newDate = lambda (lambda date : date.split('/'))(date) : '-'.join([date[i] if i != 0 else months[int(date[i])-1] for i in [1,0,2]])
# But this gives a syntax error
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 2
    lambdas can have as many arguments as you want. But also, why do this as a lambda at all, rather than just writing a normal function and making it do what you need? And of course finally, "why waste time on premature optimization" =) – Mike 'Pomax' Kamermans May 03 '23 at 16:53
  • 4
    [If you're naming a lambda, you shouldn't be using a lambda at all.](https://stackoverflow.com/q/38381556/843953) – Pranav Hosangadi May 03 '23 at 16:56
  • TBH a normal function would probably be more legible, but I've just started learning about lambda functions so I wanted to try one out – Noah Runions May 03 '23 at 16:56
  • `lambda date: datetime.strptime(date, '%m/%d/%Y').strftime('%d-%b-%Y').upper()` – jonrsharpe May 03 '23 at 16:56
  • " I use date.split('/') twice and I'd prefer to only use it once"--you can make use of the Walrus operator to only compute date.split('/') once i.e. `newDate = lambda date : '-'.join([(exp_date:=date.split('/'))[i] if i != 0 else months[int(exp_date[i])-1] for i in [1,0,2]])` – DarrylG May 03 '23 at 17:04
  • Pass `date.split('/')` as the parameter to the lambda. Then process the parts in the lambda. – Mark Tolonen May 03 '23 at 17:05
  • @DarrylG that was what I came up with too - it's brittle though, for example if you change it to `...for i in [0,1,2]` it breaks, because `exp_date` is then used before being defined. – slothrop May 03 '23 at 17:06
  • @DarrylG That still calls `split` twice and would error if `i` was zero first, e.g. `[0,1,2]` – Mark Tolonen May 03 '23 at 17:06
  • @MarkTolonen how is it calling split twice? – DarrylG May 03 '23 at 17:07
  • @DarrylG When i=1 and when i=2. – Mark Tolonen May 03 '23 at 17:07
  • A lambda *expression* is another way to define a function that could just as well be defined by a `def` statement: both create instances of `types.FunctionType`. Lambda expressions are a convenience for when you don't necessarily need a function bound to a name in the current scope. – chepner May 03 '23 at 17:09
  • With only one split and no formal nesting (though the return of 'itemgetter' really is like an inner lambda): `newDate = lambda date : '-'.join(part if j != 1 else months[int(part)-1] for j, part in enumerate(operator.itemgetter(1,0,2)(date.split('/'))))` Note that `j` here isn't the same as the original `i`. – slothrop May 03 '23 at 17:15

2 Answers2

0

It seems you're asking an XY problem as there are many better ways to convert the date string in your example.

To answer the core question of whether you can change the value of a variable in a lambda, the answer is yes starting in Python 3.8. See assignment expressions (aka the "walrus operator").

# simple example
lambda s: n if (n := len(s)) else -1

# terrible (but technically valid) example
lambda date : (d:=date.split('/'),'-'.join(d[i] if i != 0 else months[int(d[i])-1] for i in [1,0,2]))[1]

It's likely very rare that changing or assigning a variable within a lambda is a good idea. But you can with some syntactic gymnastics.

Woodford
  • 3,746
  • 1
  • 15
  • 29
  • Thanks! The cleanest solution I came up while forcing a lambda function was `newDate = lambda date : '-'.join([(date := date.split('/'))[1],months[int(date[0])-1],date[2]])` but it looks like I should just use a function def for stuff like this from now on – Noah Runions May 03 '23 at 17:32
0

There are two answers to your question.

The better answer is that you shouldn't be using a lambda here. A far cleaner solution would be:

def newDate(date):
    parts = date.split('/')
    return '-'.join([parts[i] if i != 0 else months[int(parts[i]) - 1] for i in [1, 0, 2]])

This definition of newDate can be stuck in the middle of your function (with appropriate indentation), right where you currently define assign newDate to a lambda.

If you really insist on using a lambda, you have to use the nested lambda trick. You create a second lambda that takes parts as an argument, and then call that lambda with the value of parts:

newDate = lambda date: (
                (lambda parts: '-'.join([parts[i] if i != 0 else months[int(parts[i]) - 1] for i in [1, 0, 2]])) (date.split('/')))

Your goal is to write readable code. This isn't.

Frank Yellin
  • 9,127
  • 1
  • 12
  • 22
  • Thanks for your help! I'll just stick to function defs outside of filtering lists and list comprehension from now on. Sometimes I just get so focused on trying to make everything in-line I forget to make it legible – Noah Runions May 03 '23 at 17:36