E731 do not assign a lambda expression, use a def
Asked Answered
S

8

341

I get this pep8 warning whenever I use lambda expressions. Are lambda expressions not recommended? If not why?

Skinny answered 29/7, 2014 at 7:26 Comment(2)
For clarity, the question refers to a message for an automated check in flake8 (flake8.pycqa.org)Notification
it's a pity that we're progressively being told that there's one and one only way of doing things, even though in Python that has never been the case. I wonder how that fosters creativity...Kinesics
O
401

The recommendation in PEP-8 you are running into is:

Always use a def statement instead of an assignment statement that binds a lambda expression directly to a name.

Yes:

def f(x): return 2*x 

No:

f = lambda x: 2*x 

The first 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. The use of the assignment statement eliminates the sole benefit a lambda expression can offer over an explicit def statement (i.e. that it can be embedded inside a larger expression)

Assigning lambdas to names basically just duplicates the functionality of def - and in general, it's best to do something a single way to avoid confusion and increase clarity.

The legitimate use case for lambda is where you want to use a function without assigning it, e.g:

sorted(players, key=lambda player: player.rank)

In general, the main argument against doing this is that def statements will result in more lines of code. My main response to that would be: yes, and that is fine. Unless you are code golfing, minimising the number of lines isn't something you should be doing: go for clear over short.

Oared answered 29/7, 2014 at 7:31 Comment(23)
@g33kz0r Prettier is fine in a codebase that's entirely your own (althought it's hard to guarentee that, things often migrate), but elsewhere, it's best to try and follow standards to help others read your code more effectively. Lambda holds a very specific purpose in Python and using it in this way just makes your code less readable, and objectively worse (as the answer states, you get a proper name on the function object, which is much better for debugging).Oared
not wanting to start a flame war, but statements like "using it in this way just makes your code less readable" seem highly subjective to me.Outflank
@g33kz0r At the very least, function definitions are a much more obvious and commonly used language feature than lambda, making them easier to understand. If you really think it's better in terms of readability, it's still worse in terms of functionality and for debugging.Oared
I don't see how it's worse. The traceback is still going to include the errant line number and source file. One might say "f" whereas the other says "lambda". Maybe the lambda error is easier to scan because it's not a single-character function name, or a poorly-named long name?Outflank
@g33kz0r Well, sure, if you assume the rest of your code is going to have poor quality, following conventions won't gain you much. In general, no, it's not the end of the world, but it's still a bad idea.Oared
This answer is not very helpful, because when running the suggested approach of using def through the PEP8 checker, you get E704 multiple statements on one line (def), and if you split it into two lines you get E301 expected 1 blank line, found 0 :-/Edraedrea
@AdamSpiers Putting a block on the same line is generally considered bad form - I suspect it's given like that in the PEP for similarity, but you should really split it onto two lines. Not sure what your second problem is, but it is not from that.Oared
I agree it should be split. My points were that a) it is not split in the answer's code above, causing E704, and b) if you split it, you need an ugly blank line above it to avoid E301.Edraedrea
I use lambdas when I want to emphasize a pure function (no side effects), and sometimes I have to use the same function in two places, i.e. groupby and sort together. So I ignore this convention.Misbehavior
@Misbehavior Agreed. I also assign zero argument lambdas for deferred evaluation. Example: I have a bunch of switch cases (implemented as a dictionary of lambdas), about half of them use the maximum radius; DRY means I don't want to copy-paste the definition of max radius in each case, on the other hand, I don't want to compute it pointlessly when I run cases that don't need it. So I define max radius as a zero-arg lambda.Trickery
This is a good description of the PEP-8 warning, and thus it answers the question, but I always disable E731, and in fact I recommend that others do so as well. Using def for short functions is pretty ugly. I was tasked with standardizing the application I was involved in to adhere to PEP-8 rules, but for this specific rule everybody on the team thought it was a significant change for the worse. Don't take my word for it -- write 10 different short lambda functions on 10 lines, change them to defs (one line or multiple lines), and decide for yourself. But I think the answer is clear.Laryngo
@TheGerm: I really don't understand that thinking - I don't think it's ugly at all, and using def is consistent (with longer functions, and if the function itself later becomes long), and has benefits besides (the function name for debugging). In any case, the whole point of having a standard is to provide consistency - sticking to it may not be perfect for your eye, but it will mean everyone can get on with it. Everyone is, of course, free to do what they want in their code, but I'd always recommend sticking to the standard.Oared
@GarethLatty, I am normally a big adherent to "sticking to the standard". This was just a case, though, where I (and others on my team) thought it was a direct detriment to readability. Our use case, if you care to know, was multiple short formatting functions (e.g. add percent sign, showing last name then first name) that could be applied to various grid columns.Laryngo
In my mind, if it's something you call, use def g: ... g(). If it's something you pass as an argument, use f = lambda ... h(f).Ible
Note that if you're creating these functions in a loop, using def will overwrite the last definition of the function in the loop, while lambda will create a new function each time. If you're using a loop to make a list of functions, you'll want to use lambdas, otherwise you'll only have one function under the hood.Lovesome
@Lovesome I'm not quite sure what you mean, but as far as I know (and just tried to test), that isn't true. Can you give an example of this?Oared
for sorting key in given example i prefer using operator.attrgetter to lambdaTanika
@AzatIbrakov Agreed, it's a good option alongside it's siblings itemgetter and methodcaller for relatively simple functions.Oared
Imagine you need to accept a key function as a parameter to another function. You might want the default to be something simple like lambda x: True in case the outer parameter is None. You won't be able to write the lambda in-line later when you call for filter or sort or something, so you need to make it a lambda. To define it with def would add much more code that is inside an if check for is None. But for a lambda this can be done in one line and is much, much cleaner and simpler.Tree
Kinda surprised no one has mentioned the #1 reason why you might want to assign a lambda to a variable - because python's type inference can pick up the right type for that automatically whereas for defs it cannot. That means for very simple expressions that you just want to break up and label, you not only have to use 2 lines for def, but you also have to properly type annotate which would be redundant over what inference can already figure out and can generate lots of fun bugs down the line when the type of the thing changes but you don't nkow to update the annotation god knows whereRivas
I'm getting the E731 because I allow the API caller to pass a function, a string, or None. So, I haveHenton
@Lovesome That's demonstrably not true, though it can be hard to prove to yourself. Most of the time, when we define functions in a loop, we define them all using a variable from the outer context — in which case, they'll all be defined the SAME because they're all referencing the same variable that only has one value at any given time. But it is possible to def functions in a loop that have different bodies. Here's one way: fxns = []; for i in range(5): def f(x, n=f"{i}"): return x + int(n); fxns.append(f) If you subsequently run [f(5) for f in fxns], you'll get [5, 6, 7, 8, 9].Elanorelapid
(And lambdas have the same problem, BTW. If we tried to use for i in range(5): f = lambda x: x + i; fxns.append(f) we'd find that all of our lambdas produce the same value, using whatever the current value of i is.)Elanorelapid
D
194

Here is the story, I had a simple lambda function which I was using twice.

a = map(lambda x : x + offset, simple_list)
b = map(lambda x : x + offset, another_simple_list)

This is just for the representation, I have faced couple of different versions of this.

Now, to keep things DRY, I start to reuse this common lambda.

f = lambda x : x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

At this point my code quality checker complains about lambda being a named function so I convert it into a function.

def f(x):
    return x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

Now the checker complains that a function has to be bounded by one blank line before and after.

def f(x):
    return x + offset

a = map(f, simple_list)
b = map(f, another_simple_list)

Here we have now 6 lines of code instead of original 2 lines with no increase in readability and no increase in being pythonic. At this point the code checker complains about the function not having docstrings.

In my opinion this rule better be avoided and broken when it makes sense, use your judgement.

Daugavpils answered 27/5, 2016 at 18:14 Comment(8)
a = [x + offset for x in simple_list]. No need to use map and lambda here.Strother
@Strother I believe the point was to move the x + offset portion to an abstracted location that can be updated without changing more than one line of code. With list comprehensions as you mentioned, you would still need two lines of code that contained x + offset they would just now be in list comprehensions. In order to pull those out as the author wanted, you would need a def or lambda.Polynesian
@Polynesian Apart from def and lambda one could also use functools.partial: f = partial(operator.add, offset) and then a = list(map(f, simple_list)).Strother
What about def f(x): return x + offset (i.e., a simple function defined on a single line)? At least with flake8 I do not get complaints about blank lines.Crista
@Polynesian In some cases you can use a nested comprehension: a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]Town
Also, if this is a function you only use in your own code, just prefix it with an underscore (_foo) and it will not need to have a docstring.Guidon
hold on! what sort of 2010 nonsense is this? You think you're done? Of course you are not. Now you have to add type annotations and maintain those types. And depending on your code enforcement policies you might need to add a doc comment as well.Rivas
I think walrus operator would be a good choice here, if you really care about E731Mannered
P
33

Lattyware is absolutely right: Basically PEP-8 wants you to avoid things like

f = lambda x: 2 * x

and instead use

def f(x):
    return 2 * x

However, as addressed in a recent bugreport (Aug 2014), statements such as the following are now compliant:

a.f = lambda x: 2 * x
a["f"] = lambda x: 2 * x

Since my PEP-8 checker doesn't implement this correctly yet, I turned off E731 for the time being.

Philine answered 13/1, 2015 at 17:32 Comment(2)
Even when using def, the PEP8 checker complains with E301 expected 1 blank line, found 0, so you then have to add an ugly blank line before it.Edraedrea
I don't think it's just against having assignments in lambda. I have no assignments in mine, just True or False return, yet it gives me C3001 (unnecessary-lambda-assignment) has_attribute = lambda x: x["attribute_name"] != 1 and x["attribute_name"] is not None . Looks like a pretty arbitrary stylistic decision.Degroot
A
1

I also encountered a situation in which it was even impossible to use a def(ined) function.

class SomeClass(object):
  # pep-8 does not allow this
  f = lambda x: x + 1  # NOQA

  def not_reachable(self, x):
    return x + 1

  @staticmethod
  def also_not_reachable(x):
    return x + 1

  @classmethod
  def also_not_reachable(cls, x):
    return x + 1

  some_mapping = {
      'object1': {'name': "Object 1", 'func': f},
      'object2': {'name': "Object 2", 'func': some_other_func},
  }

In this case, I really wanted to make a mapping which belonged to the class. Some objects in the mapping needed the same function. It would be illogical to put the a named function outside of the class. I have not found a way to refer to a method (staticmethod, classmethod or normal) from inside the class body. SomeClass does not exist yet when the code is run. So referring to it from the class isn't possible either.

Arrogate answered 16/6, 2016 at 14:12 Comment(8)
You could refer to also_not_reachable in the mapping definition as SomeClass.also_not_reachableCanicula
I don't know what point you're trying to make here. Every one of your function names is as reachable as f in both 2.7 and 3.5 for meSergiosergipe
Nope, all the functions, except for the lambda function, are not reachable from within the Class body. You'll get a AttributeError: type object 'SomeClass' has no attribute '...' if you try to access one of those function in the some_mapping object.Arrogate
@Arrogate all of them are perfectly accessible. The ones with @staticmethod and @classmethod don't need an object, just SomeClass.also_not_reachable (although they need distinctive names). If you need to access them from class methods just use self.also_not_reachableFaunia
@Arrogate maybe you should rename your *not_reachable methods as not_as_easily_reachable_from_class_definition_as_a_lambda xDMillicentmillie
@RomainVincent #41921755Arrogate
@Arrogate Sorry, did I miss something?Millicentmillie
All of f, not_reachable and the last defined also_not_reachable are available in the class body under their given name. For example, 'object2': {'name': "Object 2", 'func': not_reachable} works flawlessly in both Python2 and Python3. Since the functions are unbound in the class body, defining it as a regular function (no self, no staticmethod) is correct, by the way.Antisocial
L
0

The main reason seems to be that using lambda expressions to create new functions decreases code readability over using the def keyword to create those functions. If you have some code, and some functions are created with def and others with lambda, it can get confusing to tell which are functions and which are variables.

In short, the def keyword is meant to be used for creating new functions, and lambda is meant for anonymous functions (i.e. functions not tied to a name). Defining a variable as the result of some lambda expression seems to fly in the face of the intended use case for a lambda expression.

Laterite answered 13/12, 2023 at 15:40 Comment(0)
L
0

I recently encountered very legitimate reason to assign lambda expression directly (as always it CAN be avoided by creating extra code). Basically I needed to read the value of a variable at a later date, but I also need to construct something from it that the interface I use expects to be lazy-evaluated.

#my_module.py
from ... import get_global_config, Other

class Some(Other):

    SOME_MAPPING = { ... }

    def __init__(self, ...):
        some_attr = lambda: SOME_MAPPING[
            get_global_config().some_param
        ]

        ...
        super().__init__(self, some_attr, ...)

When I construct the object the global_config in question may or may not be set/constructed/fully configured, so I need to defer getting the value of the configuration to a moment when it's actually needed.

Under normal circumstances this could be avoided by using properties, however the interface I'm using/inheriting requires me to assign something concrete or a closure of type Callable[[], ...] to a particular attribute, and I didn't really feel like refactoring 1000s of lines of code at the moment.

Loricate answered 12/1 at 10:13 Comment(0)
P
-1

If you are using mypy then another reason to prefer defs is type safety.

The following code will pass mypy type checks despite the fact it contains a type error:

y = lambda x: x**2
print(y("fred"))

We can make it type-safe using the annotated code below, and now mypy will detect an error as expected.

from typing import Callable

y: Callable[[int], int] = lambda x: x**2
print(y("fred"))

However, this looks a bit unwieldy. Let's compare with the type-safe def alternative below.

def y(x: int) -> int:
   return x**2


print(y("fred"))

Arguable, the the def version is more readable and concise (objectively, although it takes two lines, it has fewer overall characters, and does not require an additional import).

Partin answered 28/7, 2023 at 16:48 Comment(8)
Use a different language if you're looking for type safety. If it's not obvious to the coder what the types needed are on this particular function, python will do nothing to ensure its "safety" -- regardless of adding your type hints or notWash
@Jon, you can run static checkers such as mypy to check for type violations. See mypy-lang.org. It is not uncommon to configure a CI pipeline to run mypy checks for Python production code.Partin
but that defies the point. All such usages are optional by design, they are not built into the language... You can modify the language to enforce type checking, but that's something else entirely -- that's not python, that's your special tool / process.Wash
PEP 484 introduces a full type system for Python. As you say, types are optional. However, if make use of them and you also make use of mypy, then you can automatically detect type errors at "compile"-time. You do not need to use a different programming language; by using mypy you can detect type errors early just as you would with a statically-typed language such as Java. Yes, you don't have to program this way in Python, but if you do want the level of type safety that you get in compiled languages the option is open to you without having to switch languages.Partin
Again, this defies the point I've been making. Yes, you can change any language to make it behave the way you want. You could modify Java so that it is loosely typed, just as you can modify python to make it strictly typed. But should you? This is a design decision and it is not as if python just "forgot" to implement type checking and they've been too busy to bother implementing it -- they never will, because there are specific benefits to embracing the design paradigms chosen. Use the tool for its intended purpose.Wash
Tying this back to your original answer, in what way are you really providing a convincing argument for using def by stating it provides "type safety", when type safety is not a feature in the language? Type hinting and safety are not the same thing, and there is a danger in implicitly thinking that hinting provides safety.Wash
I have edited my answer to clarify that the benefit only applies if you are using mypy.Partin
FYI I have not provided a downvote on this answerWash
F
-3

This works for me in a class, remove lambda expression and use def instead, changing this...

    def set_every(self, every: int = 1, time_unit: int = TimeUnit.Day):
        every_func = lambda x: "*" if x == 1 else "*/" + str(x)
        if TimeUnit.has_value(time_unit):
            self.month_of_year = "*"
            self.day_of_month = "*" if time_unit != TimeUnit.Day else every_func(every)
            self.day_of_week = "*" if time_unit != TimeUnit.Week else every_func(every)

by this...

    def set_every(self, every: int = 1, time_unit: int = TimeUnit.Day):
        def every_func(x: int) -> str: return "*" if x == 1 else "*/" + str(x)
        if TimeUnit.has_value(time_unit):
            self.month_of_year = "*"
            self.day_of_month = "*" if time_unit != TimeUnit.Day else every_func(every)
            self.day_of_week = "*" if time_unit != TimeUnit.Week else every_func(every)
Fermi answered 28/8, 2021 at 23:13 Comment(2)
Please add further details to expand on your answer, such as working code or documentation citations.Scoter
The OP never said his code doesn't work. It's only a warning, since it is a non-standard coding practiceCastleman

© 2022 - 2024 — McMap. All rights reserved.