What is wrong with defining constants as lambdas?

One need not look far to find a solution to Yacht that uses lambdas for constants (e.g.). I have heard at least one pythonista express their displeasure at this. I do not necessarily disagree, but I do wonder: why?

Why should one not define constants as lambdas in Python?

Some thoughts on expected answers (hoping for others):

  • «PEP 8 says one shouldn’t assign a lambda to an identifier»
    This is a weak argument. First, this is an appeal to authority, but the authority itself neglects to offer any reasoning. Second, PEP 8 is like the Pirates’ Code.
  • «Doing so is unpythonic»
    I tentatively agree! However, this answer is very incomplete. Why is it unpythonic? How did it become so? Should it remain so? And of course: why should I care about idiomaticity in this case?
1 Like

Linters and guidance are there to provide style guidance. Style is subjective. Though there’s often general consensus about “good” style and “bad” style. Is wearing clashing colored shirt and pants good style or bad? Who’s to say.

The lambda syntax provides an anonymous. There’s “standard” syntax for creating a named function.

def name(args):
    ...

I’ve seen code where the “regular” function syntax was not used and lambda functions preferred instead pretty widely. It hurts to read. Compare below.

class Foo:
    def __init__(self):
        self.count = 0

    def add(self, val):
        self.count += val

    def get_count(self):
        return self.count

    def get_double(self):
        return self.count * 2


class Bar:
    def __init__(self):
        self.count = 0

    def add(self, val):
        self.count += val

    get_count = lambda x: x.count
    get_double = lambda x: x.count * 2

Not sure what the exact reasoning behind PEP 8 guidelines are, but to me personally, I see no advantages to assigning lambdas to identifiers (albeit it’s often reasonable in other languages). It’s roughly the same length to write out, and makes it less obvious that the identifier is actually a callable function as apposed to a constant/variable.

Note: If you write your function definition on one line, PEP 8 will allow you to write your definition in a block without two blank lines in between.

when comparing the example you provided:

#1
YACHT = (lambda x: 50 if len(set(x)) == 1 else 0)
ONES = (lambda x: digits(x,1))
TWOS = (lambda x: digits(x,2))

#2
def yacht(x): return 50 if len(set(x)) == 1 else 0
def ones(x): return digits(x,1)
def twos(x): return digits(x,2)

Why favor the first (original) one? When taking a quick glance, the second example makes it obvious that each identifier is a function requiring one argument, whereas the first one deceitfully looks like uncallable constants.

I could not find anything on this in PEP 8. Did I miss anything?

( Not that I disagree: PEP 8’s silence can easily be taken for tacit permission. )


Too bad that popular tooling (by default) does not support the single line def style. E.g.

  • Black inserts 2–3 newlines, depending on context;
  • Pylint complains C0321 (multiple-statements): More than one statement on a single line.

I regularly see argumentation about code along these lines, but I am skeptical of it. (But see caveat below.)

It does not at all agree with my own experience: if anything, I recognize the = and the lambda …: … forms first, then register the identifier to the left soon after, and finally the body (if I choose to look at it). I do not even have to actively read for this: the ‘form’ of the text is instantly recognizable, and of course there is also syntax highlighting. I do have to read to register the identifier, and more actively so to understand the body.

Same for def: I first recognize the def … ( … ): … form (as a whole), then the name, then the arguments + body (if I choose to).

People do not tend to read letter for letter, or even strictly left-to-right. Programmers when reading code (somewhat famously) even less so. It seems really unlikely to me that large numbers or programmers would be having noticeable trouble reading code on such as small scale. ( Only 4–5 non-cramped tokens! That’s like almost subitizing scale. )

( See also: Eye movement in reading - Wikipedia, Word recognition - Wikipedia )

Caveat: I fully grant that unfamiliar code is harder to read. If you have never seen the name = lambda args: body form, then of course there will be a hiccup. The situation is the same for unfamiliar words/phrases in natural languages. Assigned lambdas are uncommon in Python, so they are more likely to be unfamiliar – even if only for a short while.


Previously I stated

But this is false: it offers two arguments. The second one is irrelevant (as it only argues that using a lambda is no better than using def), but the first one is valid:

The [def f(…): …] form means that the name of the resulting function object is specifically f instead of the generic <lambda>. This is more useful for tracebacks and string representations in general.

Perhaps not. I cannot recall the source, but it seems to conform with the PEP 8 linter in Pycharm and the pip tool pycodestyle (tested on v2.11.0), formally known as pep8, but it might come down to how strict the linter is.

That being said, I think the general thought behind the recommendation is that because a function signature is so short, a function signature should be preferred for functions over lambda assignments. PEP 8 is about staying consistent for the purpose of readability, and encourages that “When in doubt, use your best judgment.”, so there is nothing wrong with disagreeing and opting out of some recommendations.

I would like to point out one important aspect though. Using lambda assignments and function signatures aren’t the same. One becomes a function and another becomes a variable that points to an anonymous function. The latter one can be redefined, and depending on the IDE might not even be recognize as a callable function.

However, it all comes down to preference. For me personally, assigning a lambda to a variable that isn’t expected to be reassigned, doesn’t offer any benefits and compromises readability. Additionally, you also have to keep the context in your mind that the variable may mutate somewhere in the code base.

By the way, I’m not advocating against assigning anonymous functions to iterables, that can be very useful for code reduction, such as Solution: Phone Number

Are you suggesting Python doesn’t allow redefining functions?

for i in range(5):
    def a(x):
        print(x + i)
    a(3)

Apologies for misspeaking earlier. The interpreter does allow function redefinition, it only gives of a linter warning. I must have mixed my notion with another language. My bad.

The interpreter doesn’t do linting :blush: I believe linters won’t complain, either, when you redefine functions in a loop. Some linters do complain if you do the following, though:

def a():
    return 1

def a():
    return 2

Note, both create identifiers that point to functions. I’m not sure what the technical definition of a variable is in Python, but the two are not very far apart.

Python does not distinguish between identifiers that denote functions and other identifiers. They’re all just variables.

All difference is in the objects themselves. One difference is their __name__ (which is mutable and has nothing to do with variables):

def fun(): return 1 / 0
lam = lambda: 1 / 0
g = fun

print(f"{fun.__name__ = }")  #  'fun'
print(f"{lam.__name__ = }")  #  '<lambda>'
print(f"{g.__name__ = }")    #  'fun'
g.__name__ = "Hendrik"
print(f"{g.__name__ = }")    #  'Hendrik'
print(f"{fun.__name__ = }")  #  'Hendrik'
lam.__name__ = "Lama"
print(f"{lam.__name__ = }")  #  'Lama'

I haven’t verified it yet, but this might just be all.

Docstrings become more difficult to assign if you’re using a lambda. Then again, using a lambda should mean you have no use for a docstring… Interestingly, this SO poster had a need for one?

Edited to add: This SO Post answer makes an argument for type annotation/type safety being “higher” with def. YMMV.

You’re likely right, and thank you both for the examples you’ve provided. As mentioned earlier I immediately conceded the argument on the distinction between functions and variables as my reasoning must’ve been rooted in another language I learn side by side.

I do still think that it was a minor point though, and that the main argument was (and still is) a matter of readability, which of course not everyone agrees with. If they are equal, what do you think is the most compelling reason to favor lambda assignments over def when creating named functions? Perhaps, do you find lambdas assignments easier to read?

No, certainly not. I did not mean to imply that “it” was the interpreter either, so we’re not in any disagreement.

For the reasons stated in this post on discuss.python.org wrt stack traces and help functions (__repr__() and __str()__), I’d prefer not assigning a name to a lambda.

Sure – the stack traces give you line numbers, but I think I’d still prefer having a direct, traceable name (and help printout!) for most things…

As I mentioned way up in my first post, style is highly subjective. Guides often make arbitrary choices. Guides also provide guidelines and not hard rules. So long as code is consistent, I generally try not to hold strong opinions about arbitrary subjective choices.

A typical example of me reaching for an assigned lambda is

def sum_of_multiples(limit, factors):
    is_multiple = lambda n: any(n % f == 0 for f in factors if f != 0)
    return sum(filter(is_multiple, range(limit)))

Like most lambdas, this is a throw-away. I could have passed it as an argument directly, but I found that would be way less readable. So I moved it out, and as a bonus gave it a nice self-documenting name.

Why did I not use def? I’m guessing two reasons:

  • (very subjective) The def syntax feels heavier / noisier to me. Often I prefer it, but not in contexts such as the above.

    def throwaway(args): return body
    # vs.
    throwaway = lambda args: body
    

    With throw-aways, I care a lot more about their name and them being a function, and a lot less about the body and names of parameters, or even their exact number. An assigned lambda places the emphasis where I want it.

    Multiline def syntax makes it very easy to find & navigate the parameter list, and has the body jump out separately as well. When I care more about these, I use def.

  • Additionally, I was probably pushed into this habit by my formatter insisting on reformatting my code to be inappropriately spacy. When I want a single line function definition, I ‘need’ a lambda.

I suppose encountering a lambda does have some merit, by setting up an expectation of:

  • Having no/limited flow control (i.e. single expression).
  • likely to be used on (items of) an iterable collection.
  • likely to be used as throwaway by a single higher-order function.

Nice. I think I’m coming around (slightly) :sweat_smile: