|
|
Subscribe / Log in / New account

"Real" anonymous functions for Python

Did you know...?

LWN.net is a subscriber-supported publication; we rely on subscribers to keep the entire operation going. Please help out by buying a subscription and keeping LWN on the net.

By Jake Edge
March 19, 2024

There are a number of different language-enhancement ideas that crop up with some regularity in the Python community; many of them have been debated and shot down multiple times over the years. When one inevitably arises anew, it can sometimes be difficult to tamp it down, even if it is unlikely that the idea will go any further than the last N times it cropped up. A recent discussion about "real" anonymous functions follows a somewhat predictable path, but there are still reasons to participate in vetting these "new" ideas, despite the tiresome, repetitive nature of the exercise—examples of recurring feature ideas that were eventually adopted definitely exist.

At the end of January, Dan D'Avella asked why Python did not have "real anonymous functions a la JavaScript or Rust". While Python functions are regular objects that can be assigned to a variable or passed to another function, that is not reflected in the syntax for the language, in his opinion. There is, of course, lambda, "but its usefulness is quite limited and its syntax is frankly a bit cumbersome". He wondered if more flexible, full-on anonymous functions had been proposed before, saying that he had not found any PEPs of that nature.

Python has two ways to define functions, def for named functions and lambda for anonymous functions. While named functions can contain multiple statements, lambda is an expression that cannot contain any statements; it returns a function object that evaluates an expression with the parameters given:

    >>> (lambda x: x * 7)(6)
    42

That creates an anonymous function and calls it with a parameter of six, but that is not a particularly idiomatic use of lambda. As described in an LWN article on proposals for alternative syntax for lambda, a common use of the facility is to provide a way for some library functions to extract the actual arguments they should use, as with:

    >>> tuples = [ ('a', 37), ('b', 23), ('c', 73) ]
    >>> sorted(tuples, key=lambda x: x[1])
    [('b', 23), ('a', 37), ('c', 73)]

That uses a lambda expression to extract the second element of each tuple, which is used as the sort key.

In a response to D'Avella, David Lord wondered if he was asking for "multi-line lambdas", which is an idea that has come up many times over the years. While a lambda expression can actually span more than one physical line, using the \ line-continuation character, the restriction to a single expression greatly limits what lambdas can do. For more complex computation, named functions, which can be limited to the local scope to avoid any name-collision problems, are the way to go, as a 2006 blog post from Guido van Rossum stated. In his opinion, there is no Pythonic way to embed a multi-statement construct in the middle of an expression.

Lord pointed out that "there are years if not decades of previous iterations" of discussions about multi-statement lambdas; he linked to Van Rossum's post to show some of that history and why it is believed that there are unsolvable problems in providing the feature. The problem, in a nutshell, is that Python blocks are delineated using white space, not braces or keywords like begin and end, so any expression used to create a function with multiple statements would need a way to incorporate white space—or it will not look like Python at all. Van Rossum, though, said that even if acceptable syntax could be found, he still objected to the idea of adding lots of complexity to lambda in order to avoid something that is "only a minor flaw".

D'Avella said that Van Rossum's opinion may have been justified 18 years ago, but may no longer be so, "especially in light of developments in other languages". Beyond that, Python itself has changed quite a bit over that time; "Try to imagine using match in Python" back when that post was written. While it is true that there have been plenty of changes in the intervening years, D'Avella did not specify how those changes provided reasons to modify the longstanding decision to maintain the restrictions on lambda, as several in the thread noted.

Terry Jan Reedy said that one of the hallmarks of Python is its readability and that adding more complicated lambda expressions runs counter to that. Both for debugging and testing purposes, named functions are superior, except for the simplest expressions: "Lambda expressions work best when obvious enough to not need testing and when unlikely to be a direct cause of an exception." In practice, the current lambdas are generally expressive enough, Reedy said.

D'Avella dismissed that as a "perfectly valid" opinion, but one that he disagreed with; based on his experience with other languages, he believes that "anonymous function literals are an elegant and useful construct". The problem, though, as Paul Moore pointed out, is that there is a process that needs to be followed so that the core developers will reconsider a longstanding decision of this nature; someone needs to explain why the objections raised before are no longer valid:

Maybe if you picked one or two of those, and explained precisely why they don't apply any more, that might be a better argument than simply "everything changes, so let's propose something that's been rejected before, one more time".

There may be perfectly good reasons why Python should now support anonymous functions. But if no-one ever puts together a proposal that actually addresses the reasons why this idea has failed so many times before, we'll never know.

Part of the problem is that it is difficult to track down the previous discussions of the feature, D'Avella said. He had expected to find a rejected PEP, but did not; his searches for other discussions were not particularly successful either. Reedy pointed to a number of different sources for discussions, which are scattered over several mailing lists and, more recently, the Ideas category on the Python forum. Part of the disconnect may be that D'Avella is wondering about "anonymous functions", but the discussions in Python circles generally revolve around lambda, since that is the existing mechanism in the language. One suspects he would have had more success searching using the term "lambda" since a multi-statement lambda is a real anonymous function, as he agreed.

More links to previous discussions can be found in a post from Mike Miller. A thread he pointed to leads to a 2009 post from Alyssa Coghlan that "sums up the major problem" with any proposal, according to Eric V. Smith. Coghlan said:

However, for Python, the idea of having anonymous blocks runs headlong into the use of indentation for block delineation. You have to either come up with a non-indentation based syntax for the statements inside the anonymous blocks, essentially inventing a whole new language, or you have to embed significant whitespace inside an expression rather than only having it between statements.

Nobody has come up with a solution to that mismatch which is superior to just naming the block and using a normal def statement to create it.

For Smith, there is no point in looking further until that is resolved:

I know people will disagree, but for me, if the syntax problem were resolved, then I'd welcome "expressions that produce functions consisting of multiple statements", commonly called multi-line lambdas.

But without a solution to the syntax issue, the whole argument is not worth having.

D'Avella took a stab at some possible syntax for the feature, which, naturally, relied on delimiters around the anonymous block. He used do and end, but others have tried parentheses or brackets, all of which run aground because they do not look "Pythonic", at least to some. Meanwhile, Clint Hepner argued that some seem to simply want anonymous functions for Python because they are used frequently—and liked, at least by some—in JavaScript. D'Avella, playing the devil's advocate, wondered what the problem was with that line of thinking; multiple different Python features have come from elsewhere after all. But Brendan Barnwell said that those features came to Python, not simply because they existed elsewhere, but because they were useful in the language:

They were added because another language provided some inspiration for a feature to add to Python that might be helpful when writing code in Python. It's totally great to have interchange of ideas between different languages, but each language will choose the features that mesh well with its existing constructs.

In order to try to head off future proposals of this sort, Neil Girdhar suggested adding a page to the Python wiki that would simply describe the proposed change, its pros and cons, and have links to earlier discussions. Despite some skepticism that a page of that nature would actually help, he created a page for commonly suggested features and linked a new page for multi-line lambdas to it. Meanwhile, D'Avella's attempt to attract a core developer to sponsor a PEP that he would write ("with the expectation that it will be rejected") seems to have gone nowhere.

There were some examples of possible use cases for the feature, including one from Chris Angelico and another from "Voidstar", but Cornelius Krupp pointed out that, once again, it is not use cases that are lacking. The use cases need to be compelling enough to overcome the other, syntactic, objections. While D'Avella agreed with that, he noted that there were questions about the justification for the feature in the thread. Krupp said that the syntax was just the first hurdle: "Even if people find a good syntax there is still quite a bit of work to do in [convincing] the python community and the core devs that it is actually a good idea."

Python famously uses white space, rather than delimiters, to structure its code and it is hard to imagine that changing—ever. Python itself will happily agree:

    >>> from __future__ import braces
      File "<stdin>", line 1
    SyntaxError: not a chance

Meanwhile, the cost of a name, quite possibly in a local scope, is pretty low; the readability of the resulting code is likely to be better as well. There would certainly be value in having a rejected PEP to point to when the topic inevitably rears its head again, though it may be hard to motivate people to work on a "doomed" proposal. The topic will undoubtedly be raised again, though; as Krupp pointed out, it has come up at least three other times in the last few years.

It is, however, a persistent problem in the Python world; people show up with new ideas, or one that has been discussed time and time again, without really wanting to put in the work to see it to completion. Sometimes that work is in finding the earlier discussions and showing why the objections raised then are no longer valid—using examples of where existing code, perhaps from the Python standard library, could be improved. But as we have seen before, more than just once, people are often so enamored with their idea that they are surprised that it does not simply sweep all objections aside because of its clear superiority. In a long-established language or other project, however, ideas take a lot of work before they bear fruit; proponents would be well-advised to keep that in mind.


Index entries for this article
PythonDevelopment process
PythonLambda


(Log in to post comments)

Clear superiority of the idea

Posted Mar 19, 2024 21:38 UTC (Tue) by bignose (subscriber, #40) [Link]

> […] people are often so enamored with their idea that they are surprised that it does not simply sweep all objections aside because of its clear superiority. In a long-established language or other project, however, ideas take a lot of work before they bear fruit; proponents would be well-advised to keep that in mind.

This is wisdom that humanity, broadly, would greatly benefit from.

"Real" anonymous functions for Python

Posted Mar 19, 2024 23:14 UTC (Tue) by nowster (subscriber, #67) [Link]

I don't think it's multi-line lambdas that are needed, as you can already do
f = lambda x: (
    x
    +
    1
)
What I think are wanted here are multi-statement lambdas.

"Real" anonymous functions for Python

Posted Mar 21, 2024 13:46 UTC (Thu) by NRArnot (subscriber, #3033) [Link]

You can get somewhat close to multi-statement lambdas using tuples and the walrus operator if necessary. This covers a lot of the ground needed for callback functions, which is the only place I have used this "trick".
      foo = lambda x:   ( z:=x+1,  zz:=z*2,  zz+7 )[-1]
      assert foo(5) == 19
Two assignment statements, and the last expression in the tuple as its return value. Personally I feel this goes far enough. (Too far?) If there's a need for looping or conditionals that aren't trivially expressible as list comprehensions and/or conditional assignments, it probably really ought to be a named function.

"Real" anonymous functions for Python

Posted Mar 20, 2024 0:21 UTC (Wed) by Nahor (subscriber, #51583) [Link]

> people are often so enamored with their idea that they are surprised that it does not simply sweep all objections aside because of its clear superiority

Indeed, I too do not understand this love of whitespaces and the "Pythonic" way as a sweeping reason to reject a feature so clearly useful that multiple languages (https://en.wikipedia.org/wiki/Anonymous_function#List_of_...) have chosen to adopt it, either from the get-go, or retrofitted.

:p

Seriously though, IMHO, this kind of jab is unbecoming of a LWN article, regardless of the level of frustration the author might be feeling.

"Real" anonymous functions for Python

Posted Mar 20, 2024 8:09 UTC (Wed) by LtWorf (subscriber, #124958) [Link]

It is a rather useless feature in python, because you can just define a function one line above, and it will be visible only within that scope.

For languages where you can't just define functions wherever you want, they make much more sense.

"Real" anonymous functions for Python

Posted Mar 20, 2024 8:32 UTC (Wed) by taladar (subscriber, #68407) [Link]

The fact that you can work around the shortcomings of the language doesn't mean that it is not a flaw in the language design.

Most other languages that do have proper anonymous functions also allow you to locally declare named functions but naming things is hard and it just breaks up the code reading flow in a way that requires constant jumping back and forth to understand it.

"Real" anonymous functions for Python

Posted Mar 20, 2024 10:01 UTC (Wed) by LtWorf (subscriber, #124958) [Link]

> The fact that you can work around the shortcomings of the language doesn't mean that it is not a flaw in the language design.

And why isn't the shortcoming the inability to define a function inside a function of other programming languages?

> but naming things is hard

"f"

> reading flow

lambdas and comprehensions aren't particularly readable already. Making them multi statement wouldn't make them more readable.

"Real" anonymous functions for Python

Posted Mar 20, 2024 13:05 UTC (Wed) by elaforma (subscriber, #165356) [Link]

> And why isn't the shortcoming the inability to define a function inside a function of other programming languages?

Because languages that don't have that particular shortcoming (e.g., Rust, C#, ...) also have anonymous functions.

It is simply quite useful to be able to define a function in expression position. Not sure how Python could create a coherent syntax for it (that fits in with the rest of the language), but if they find a way to make it work, it would be nice to have.

"Real" anonymous functions for Python

Posted Mar 20, 2024 16:14 UTC (Wed) by paulj (subscriber, #341) [Link]

"Other languages have XYZ!" is a pathway to a mess. Unfortunately, most languages eventually succumb to this.

Code littered with large anonymous functions is typical in some languages these days - method after method, calling something and passing off big chunks of logic as an anon function. And I'm not sure it helps at all with readability.

The outer-level logic becomes hard to see - i.e. the pattern of method calls, and logic arising from their return values and/or state changes in data modified by the anon function while called by the called method. The logic in all the callbacks and filter functions of the anon functions often would be easier to understand if properly grouped together (in some languages the anon logic may already be implementing one or more methods of some defined interface - common in Java), and not nested in other logic.

But hey... this is what the cool kids do these days, so $OUR_LANGUAGE should do it too (even if it's trivial to just define a nested function - the scoped naming is no more an issue than any other locally scoped variable!).

"Real" anonymous functions for Python

Posted Mar 21, 2024 2:11 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

> But hey... this is what the cool kids do these days, so $OUR_LANGUAGE should do it too (even if it's trivial to just define a nested function - the scoped naming is no more an issue than any other locally scoped variable!).

These days the popular design is to move from the nested-callbacks-within-callbacks pattern, in favor of async/await. Which Python already has, of course.

"Real" anonymous functions for Python

Posted Mar 23, 2024 0:00 UTC (Sat) by tialaramex (subscriber, #21167) [Link]

Notice that Rust's closures have different functionality from a function.

let consumer = move || x;

This type can be called, once, and in doing so it gives you x, whatever that is, and now it doesn't have anything so it can' t be called again. Rust has a trait for this, FnOnce, and many places in Rust only require FnOnce for a parameter since they know they're only going to call it once.

It's fine to provide a function (which can be called over and over) when you were asked for only FnOnce, whereas it's not fine to provide our consuming closure (which can only be called once) when asked for FnMut, the trait for a callable that has mutable state but can still be called many times -- that won't compile.

"Real" anonymous functions for Python

Posted Mar 20, 2024 15:42 UTC (Wed) by draco (subscriber, #1792) [Link]

You could also name it after the argument it's supposed to fill

"sorter"
"visitor"
"callback"

I often feel terrible at names, but I've never struggled to name my interior functions. That they only need to be good enough for that scope often helps.

"Real" anonymous functions for Python

Posted Mar 21, 2024 9:52 UTC (Thu) by SLi (subscriber, #53131) [Link]

I think a reasonable argument goes something like: Languages can optimize for different things. Generally, you cannot have everything. There will always be trade-offs. For example, if one of your goals was to develop a minimalistic language, there will always be features that would be useful, but which will detract from the minimalism of the language.

One aspect Python has chosen to optimize for is a specific kind of syntax. Now, many voices in the Python developer community seem to admit that multi-statement lambdas might improve the language if you were able to keep all the other aspects the same. But nobody has been able to come up with a proposal that does not, in the view of this community, sacrifice too much other virtues to get what is perceived to be a minor improvement in not having to make a local named function.

Thus, they argue, inventing "non-pythonic" syntax to allow for multi-statement lambdas would make the language worse, probably sacrificing certain kinds of things like simplicity and readability.

"Real" anonymous functions for Python

Posted Mar 20, 2024 17:16 UTC (Wed) by Phantom_Hoover (subscriber, #167627) [Link]

Where does this idea that multiline/multistatement lambdas are commonly used in other languages come from? Even in Haskell I think a multiline do-block would be ugly and awkward, and better off lifted into a where or let binding.

"Real" anonymous functions for Python

Posted Mar 20, 2024 17:18 UTC (Wed) by Phantom_Hoover (subscriber, #167627) [Link]

(I mean a multiline do inside a lambda, of course.)

"Real" anonymous functions for Python

Posted Mar 25, 2024 0:24 UTC (Mon) by spigot (subscriber, #50709) [Link]

Mmm, not sure what you have in mind, but multi-line do blocks in lambdas are very common in Haskell, especially as the last argument to a function that returns IO a. E.g.
    ...
    case args of
      [file] -> withFile file ReadMode $ \h -> do
        degs <- map read . lines <$> hGetContents h
        mapM_ printPointName degs
    ...

"Real" anonymous functions for Python

Posted Mar 23, 2024 13:35 UTC (Sat) by jezuch (subscriber, #52988) [Link]

Multi-statement lambdas in Java are a small minority, but they are not rare either. To the point that disallowing them would make the feature close to useless. (Though I can imagine some purist arguing that it would "encourage" / enforce the "good practice" of declaring a separate function for that code. As I can imagine such purists arguing that in Python land.)

"Real" anonymous functions for Python

Posted Mar 23, 2024 13:45 UTC (Sat) by LtWorf (subscriber, #124958) [Link]

Well the mantra of java is: "never write something in 1 line when you can write it in 150 lines", so I'm not surprised.

"Real" anonymous functions for Python

Posted Mar 20, 2024 13:06 UTC (Wed) by ballombe (subscriber, #9523) [Link]

A compiler can perform a number of easy optimizations with anonymous functions that cannot be done with named function unless the compiler is smart enough to inline the call, which is easy to do
in pure functional languages but much less in languages with side-effects.

Another issue is that you can inadvertently pick a name that is already in use and that cause issue down the line.

"Real" anonymous functions for Python

Posted Mar 21, 2024 9:59 UTC (Thu) by SLi (subscriber, #53131) [Link]

If it's a local function inside a function, it wouldn't take too much intelligence from a compiler to see that it's used exactly once. Furthermore, I don't think this is more true about lambdas than local named functions because lambdas can also be referenced from multiple places. The following pieces of code are the same:

def sort_by_mod(xs):
    def inner(x):
        return x % 100
    return sorted(xs, key=inner)
def sort_by_mod(xs):
    inner = lambda x: x % 100
    return sorted(xs, key=inner)
def sort_by_mod(xs):
    return sorted(xs, key=lambda x: x % 100)

"Real" anonymous functions for Python

Posted Mar 22, 2024 12:43 UTC (Fri) by ballombe (subscriber, #9523) [Link]

So, is the python bytecode compiler smart enough to handle them the same way ?

"Real" anonymous functions for Python

Posted Mar 22, 2024 13:20 UTC (Fri) by SLi (subscriber, #53131) [Link]

I believe CPython does almost no inlining in any case (and doesn't compile to anything like native code), although there is some very limited partial inlining in some recent versions.

I also believe the interpreter has no special knowledge of the fact that `sorted` does not store the passed function somewhere and call after the containing function has returned, which is completely legal in Python, so it seems to me that it must by necessity pass some kind of a boxed function object on the heap. In fact, the compiler *cannot* know that `sorted` doesn't do that, since given the dynamic nature of Python you could replace `sorted`.

"Real" anonymous functions for Python

Posted Mar 23, 2024 8:00 UTC (Sat) by NYKevin (subscriber, #129325) [Link]

I believe CPython does almost no inlining in any case (and doesn't compile to anything like native code), although there is some very limited partial inlining in some recent versions.

I would go further than that. Python bytecode is almost totally unoptimized, aside from basic constant folding and peephole optimizations. For example:

>>> import dis
>>> def f():
...   x = 3
...   return x in (1, 2, 3)
...
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (3)
              2 STORE_FAST               0 (x)

  3           4 LOAD_FAST                0 (x)
              6 LOAD_CONST               2 ((1, 2, 3))
              8 CONTAINS_OP              0
             10 RETURN_VALUE

When compiling this function, Python:

  • Emits code to store and load x even though its value never changes.
  • Does recognize that (1, 2, 3) is a compile-time constant and can be pre-allocated (it does not emit code to construct the tuple).
  • Does not even attempt to unroll the comparison, which might otherwise have enabled further optimizations.
  • Does not optimize it into return true even though that's perfectly equivalent.

Well, actually that last bullet is a tiny bit of a lie, because it is theoretically possible to monkey-patch the tuple type. It is not easy to do so, since it's implemented in C, and such chicanery is certainly a bad idea in any event. But it can be done, I think, and I suppose the implementation might want to allow for that. On the other hand, the way you do it is "reach into the CPython PyTypeObject struct for the tuple type and mess with its tp_richcompare field," so IMHO the implementation would be well within its rights to declare this unsupported and refuse to preserve the operator.

"Real" anonymous functions for Python

Posted Mar 23, 2024 13:44 UTC (Sat) by LtWorf (subscriber, #124958) [Link]

I don't think it is possible to monkey-patch tuple.

It's implemented in C.

You can do tuple=mytuple, but the syntax (x,y) will always call the regular one, not your class. That will happen only if you call tuple() explicitly.

"Real" anonymous functions for Python

Posted Mar 23, 2024 14:55 UTC (Sat) by NYKevin (subscriber, #129325) [Link]

As I mentioned, you can manually overwrite the function pointer in the real tuple's type object at the C level. This is a terrible idea, and nobody should actually do it.

"Real" anonymous functions for Python

Posted Mar 20, 2024 8:29 UTC (Wed) by taladar (subscriber, #68407) [Link]

That kind of reaction really does explain why so much tooling in the Python world is so fragmented. The language culture seems to have a problem with working together constructively.

"Real" anonymous functions for Python

Posted Mar 20, 2024 10:27 UTC (Wed) by Wol (subscriber, #4433) [Link]

> Indeed, I too do not understand this love of whitespaces and the "Pythonic" way as a sweeping reason to reject a feature so clearly useful that multiple languages (https://en.wikipedia.org/wiki/Anonymous_function#List_of_...) have chosen to adopt it, either from the get-go, or retrofitted.

The problem is it is NOT whitespace.

I'm not a python programmer, but as I understand whitespace, it is meaningless filler that doesn't do anything. Python DOESN'T HAVE whitespace (unless it's blank lines between statements, maybe). I was brought up programming in FORTRAN. The first six spaces on a line there aren't whitespace, either.

So this is actually a serious semantic discussion - you are taking a functional component of the language, AND CHANGING ITS MEANING.

To an outsider, yes it does look stupid. But when you look into the real problem, there really is one.

Cheers,
Wol

"Real" anonymous functions for Python

Posted Mar 20, 2024 15:57 UTC (Wed) by draco (subscriber, #1792) [Link]

Byyourdefinition,whitespacedoesn'texistsinceallwh
itespaceaffectsparsing.Imean,ifyou'regoingtobepe
dantic…

😁

"Real" anonymous functions for Python

Posted Mar 21, 2024 13:49 UTC (Thu) by ejr (subscriber, #51652) [Link]

Spacing between words, etc. only began in the 7th century (iirc) and wasn't adopted widely until 3-4 centuries later.

"Real" anonymous functions for Python

Posted Mar 20, 2024 20:31 UTC (Wed) by rschroev (subscriber, #4164) [Link]

What do you mean by "Python DOESN'T HAVE whitespace"? What do you mean by "it's meaningless filler that doesn't do anything"?

Python does have whitespace, and it is meaningful. Python uses whitespace between tokens to separate them where needed, and it uses whitespace as indentation to indicate the structure of the code. See sections 2.18 (https://docs.python.org/3/reference/lexical_analysis.html...) and 2.1.9 (https://docs.python.org/3/reference/lexical_analysis.html...) of the Language Reference.

I'd think you have at least some kind of idea of how Python works (otherwise why would you even write comments on its use of whitespace), so I'm perplexed by those statements of yours.

> I was brought up programming in FORTRAN. The first six spaces on a line there aren't whitespace, either.

I don't think you can compare the syntax of FORTRAN and Python in any meaningful way. In FORTRAN you don't need whitespace between tokens: you can write "10101 DO 101 I = 1, 101" as "101010DO101I=1,101". In Python you can do no such thing.

(When I first heard of Python and it's use of significant whitespace, it reminded me of the fixed formatting of FORTRAN, and that initially put me of learning Python. When I eventually started to learn Python anyway, I quickly learned that my fears where completely unfounded.)

"Real" anonymous functions for Python

Posted Mar 21, 2024 12:41 UTC (Thu) by Wol (subscriber, #4433) [Link]

Please read, and comprehend, what I *actually* wrote.

You're replying to a strawman.

Thanks.

(Oh, and I know I have a tendency to do that myself, unfortunately :-)

Cheers,
Wol

"Real" anonymous functions for Python

Posted Mar 21, 2024 19:40 UTC (Thu) by rschroev (subscriber, #4164) [Link]

You *did* write this:

> I'm not a python programmer, but as I understand whitespace, it is meaningless filler that doesn't do anything. Python DOESN'T HAVE whitespace (unless it's blank lines between statements, maybe). I was brought up programming in FORTRAN. The first six spaces on a line there aren't whitespace, either.

In the first two sentences, you say that whitespace in Python is meaningless filler that doesn't do anything, and that Python doesn't have it. That completely clashes with the way I see whitespace in Python, so either one of us is wrong, or we're using different definitions for whitespace and what it means for a programming language to have meaningful whitespace. I'm interested to know which is which, which is why I asked for clarification.

In the last two sentences you compare whitespace in FORTRAN with whitespace in Python. This is one thing I do know for sure: they are not alike at all. That only increases my confusion. It led me to think, perhaps wrongly, that you think the two are comparable. I might have gone a bit too far with that assumption; if so, I apologize. Still, I'm interested to know why you bring up FORTRAN (out of many to choose from) when talking about Python and its whitespace.

I really don't see how I replied to a strawman. Please educate me.

"Real" anonymous functions for Python

Posted Mar 21, 2024 23:05 UTC (Thu) by Wol (subscriber, #4433) [Link]

There are two (and probably more) understandings of the term "whitespace".

There is the human definition - "characters I can't see on screen", and the computer one - "filler that is discarded by the lexer".

In MOST languages, C in particular, those two definitions are almost the same. Strip the human-definition-whitespace out of a C program, and it will probably still compile without error.

In Python, those two definitions are almost completely disjoint. Pretty much all human-whitespace in Python has syntactic or semantic meaning to the compiler or interpreter - strip it out and you have completely corrupted the program to the extent that it almost certainly won't run, and even if it gets that far it won't do what you meant it to do.

> In the first two sentences, you say that whitespace in Python is meaningless filler that doesn't do anything, and that Python doesn't have it

And this is the root of the strawman. I said *IF* we define whitespace as meaningless filler, then most of what we humans consider whitespace is - from Python's point of view - NOT whitespace.

And THIS is the root of the problem with the idea of anonymous functions - whereas MOST languages don't care about human whitespace because it's the same thing (almost) as computer whitespace, Python cares deeply because it is NOT.

(And that is why I said you were arguing against a strawman, because you conflated the two different definitions of whitespace.)

Cheers,
Wol

"Real" anonymous functions for Python

Posted Mar 23, 2024 1:12 UTC (Sat) by Phantom_Hoover (subscriber, #167627) [Link]

I think the underlying point is that in Python, syntax is quite closely coupled to formatting, which means that a seemingly trivial syntactic extension like ‘lambdas with blocks of statements in the body’ unavoidably means code with very very clunky formatting. Because blocks of statements in Python are a series of lines with a common indentation, and recursively nesting them inside an expression is ugly as hell and not easy to reason about.

"Real" anonymous functions for Python

Posted Mar 24, 2024 1:25 UTC (Sun) by himi (subscriber, #340) [Link]

I think Wol is using a particular definition of "whitespace" here: visually empty space that's not semantically significant. And it's a meaningful distinction - there's a real difference between "whitespace" in this sense, and things like indentation in Python, even if they share the same set of characters. The use of [:space:]+ to separate tokens where required also wouldn't qualify as whitespace by this definition.

I'm not sure I've seen this definition used explicitly anywhere, but it makes perfect sense to me . . .

"Real" anonymous functions for Python

Posted Mar 25, 2024 12:26 UTC (Mon) by Wol (subscriber, #4433) [Link]

>I think Wol is using a particular definition of "whitespace" here: visually empty space that's not semantically significant. And it's a meaningful distinction - there's a real difference between "whitespace" in this sense, and things like indentation in Python

Eggsackerly

> The use of [:space:]+ to separate tokens where required also wouldn't qualify as whitespace by this definition.

Mmmm ... [:space:] may not be whitespace, but in MOST modern languages, if you see [:space:] [:space:]+, then the [:space:]+ would be whitespace (as per my definition). But in FORTRAN, and aiui Python, [:space:]n is semantically different from [:space:]n+1, and most definitely NOT whitespace by that definition.

Cheers,
Wol

"Real" anonymous functions for Python

Posted Mar 25, 2024 15:51 UTC (Mon) by anselm (subscriber, #2796) [Link]

But in FORTRAN, and aiui Python, [:space:]n is semantically different from [:space:]n+1, and most definitely NOT whitespace by that definition.

Fortran (up to Fortran 77) stipulated that the first five columns of the punched card could contain a statement label or would be left blank. Similarly, the sixth column would be non-blank iff the card in question was a continuation of the previous card. In columns 7–72, whitespace would be entirely optional. (Columns 73–80 were ignored.) The spaces-are-insignificant thing could lead to issues like, famously, the construction

      DO 10 I=1.10
          WRITE (*,*) 'I = ', I
10    CONTINUE
which – even if superficial appearances would suggest otherwise – is not actually a loop as far as Fortran is concerned.

In Python, space and tab characters are only really significant at the start of a line, because Python eschews the curly braces or similar start-block/end-block markers popular with other languages in favour of indentation. IOW, where a language like C would use

for (i=0; i < 10; i++) {
    printf("%d\n", i);
}
printf("Done.\n");
Python would just say
for i in range(10):
    print(i)
print("Done.")
(People tend to either love or hate that idea, but it isn't unique to Python.) In Python, after the first non-space/tab character, additional spaces or tabs farther right on the line fall under your “whitespace” definition.

So, for (older versions of) Fortran, spaces are mostly irrelevant except in the first six columns of a punched card. In Python, spaces are mostly irrelevant, except as far as indentation is concerned. In both languages, most “intra-line” spaces are “whitespace” by your definition just like in the vast majority of other languages.

"Real" anonymous functions for Python

Posted Mar 20, 2024 23:59 UTC (Wed) by Paf (subscriber, #91811) [Link]

“> people are often so enamored with their idea that they are surprised that it does not simply sweep all objections aside because of its clear superiority”

I don’t think this is a jab at all - I think it is a succinct and deeply sound observation about people.

"Real" anonymous functions for Python

Posted Mar 21, 2024 0:39 UTC (Thu) by Nahor (subscriber, #51583) [Link]

> I don’t think this is a jab at all - I think it is a succinct and deeply sound observation about people.

If it was in a neutral context I would have agreed. But here it's clearly aimed at the people making request to the Python team and that why I think it's inappropriate. It creates "them vs us" mentality.

"Real" anonymous functions for Python

Posted Mar 20, 2024 5:00 UTC (Wed) by ceplm (subscriber, #41334) [Link]

> Beyond that, Python itself has changed quite a bit over that time; ""Try to imagine using match in Python"" back when that post was written.

So, one bad decision is used as a justification for another one?

"Real" anonymous functions for Python

Posted Mar 20, 2024 8:20 UTC (Wed) by edomaur (subscriber, #14520) [Link]

Not a justification, but a proof that features in a language are not a list written in stone without any chance of change. And typically, programmers are not very flexibles with new elements of language, e.g. in the Go world the generics are still an issue even if it's really effing useful. About match, personnaly I like it, it's not perfect and somewhat as pythonic in my view as the various comprehensions things are, but it do the job and relativelly well indeed.

"Real" anonymous functions for Python

Posted Mar 21, 2024 2:12 UTC (Thu) by bjlucier (subscriber, #5281) [Link]

I generally program in Scheme, and most of my uses of anonymous functions are of the "expression" type found in Python.

However, there was one GCD algorithm by Arnold Schönhage where the control-flow logic was so spaghetti-like in its structure that I ended up using anonymous lambdas in explicit continuation-passing style; the code is here:

https://github.com/gambit/gambit/blob/536d5a121e794f8788c...

I'm not a Python programmer, so I had to look up whether Python has goto's, and I discovered that, like Scheme, it does not.

I think pulling out, naming, and trying to puzzle back together the multi-statement lambda's in that procedure would be difficult. Perhaps it would be harder than trying to untangle the control flow.

I'm not saying that that GCD algorithm is the killer example for multi-statement anonymous functions, but it's the one thing in my programming experience where I used such beasts by necessity.

"Real" anonymous functions for Python

Posted Mar 22, 2024 2:27 UTC (Fri) by jrw (subscriber, #69959) [Link]

One use case for anonymous functions is building a table of statement-functions or expression-functions. It would indeed be cumbersome to have to name each function in order to put a reference to it into a table. Often the functions you need can be written as expressions, so you're all set. But equally often, it's more convenient to write an anonymous statement-function (which might or might not generate a result with an expression) to put into your table. This is the use case that I would really miss having general anonymous functions for.

"Real" anonymous functions for Python

Posted Mar 22, 2024 15:57 UTC (Fri) by pj (subscriber, #4506) [Link]

Having done this multiple times, it's not that bad. The naive pattern typically ends up looking like:
```

def handle_a():
...

def handle_b():
...

DISPATCH = {
'a': handle_a,
'b': handle_b,
...
}
```
and while it's not super elegant, it's very clear.
But you can also mark all the handlers with annotations like, say, flask does:
```

@handle('a')
def handle_a():
...
```

...with the `handle` annotation building the dispatch table for you.

"Real" anonymous functions for Python

Posted Mar 24, 2024 21:37 UTC (Sun) by NYKevin (subscriber, #129325) [Link]

IMHO Python's most pressing issue is not the lack of extensible syntax, but the excess. It is very, very easy to start building things like the @handle decorator you describe, and five years later, your code still superficially looks like Python, but its semantics are almost totally divorced from what any reasonable Python programmer would expect.

Here are some examples of "evil" things that you can do with Python's extensible syntax:

* Decorators can add or subtract arguments, so that the function's call site does not match its signature. I have seen this used to propagate tramp data without having to manually pass an extra argument around.
* There are a variety of clever things you can do with operator overloading, but even the stdlib takes part in this chicanery (see pathlib), so I guess we can't call it evil anymore.
* Metaclasses, generally. Especially now that __init_subclass__() and __set_name__() exist and you no longer need to use metaclasses for those use cases. The remaining use cases tend to look like "I want a config language like YAML/TOML/etc., but I also want loops and abstractions so that I don't have to laboriously write the same thing over and over again. I could use Python, but it doesn't have stanzas with the semantics I want... so I'll just make a metaclass that turns classes into stanzas."
* The descriptor protocol and the __getattr__()/__setattr__() methods can be used to modify the behavior of foo.bar in a variety of surprising ways. For example, you could have the assert fail in foo.bar = baz; assert foo.bar == baz. Fortunately, these semantics are quite basic and obvious to most programmers, so it's not very hard to convince people that they need to get it right. However, if baz is a mutable object, then the semantics are significantly harder to get right in the general case. Some implementations respond to this by throwing their hands up and saying "fine, you can only use the nice syntax for immutables. For mutables, you have to use this ugly different syntax."
* All of the above can be used together and tightly coupled so that the system as a whole only ever "works" if all of the necessary syntax extensions are combined in the right way. If you forget one of them, then you get an obscure exception from deep within your bespoke desugaring stack (when you've gone this far, it's complicated enough to constitute a "stack"), usually complaining that some object you've never heard of is missing or of the wrong type. Also, the documentation for the Right Way is often incomplete or entirely nonexistent.

I like Python overall. I just wish people would stop using it as a platform for inventing not-Python lookalikes.

"Real" anonymous functions for Python

Posted Mar 21, 2024 7:14 UTC (Thu) by jem (subscriber, #24231) [Link]

I am happy with Python's lambda expressions as they exist today, and do not see any need to complicate the language with new syntax for multi-statement anonymous functions. Anonymous functions should be used for "doing one thing". Multi-statement functions are mostly needed for side effects, and if you have side effects, then the function is not doing just one thing anymore.

To improve readability, anonymous functions that are created on the spot should have simple syntax so that it is immediately obvious what is going on when they pop up in the code. Anything that doesn't fit in one statement deserves a name, not least because the name documents what the function's purpose is. Arguing that naming is hard is just a sign of laziness.

"Real" anonymous functions for Python

Posted Mar 21, 2024 10:55 UTC (Thu) by kpfleming (subscriber, #23250) [Link]

A part of the problem here, which is also true in thousands of other contexts, is that the Python project generally keeps explicit records of decisions to *do* a thing, but doesn't keep records of decisions *not* to do a thing (except in the case of PEPs that are rejected, but not all discussions graduate to the PEP level).

While it's very management-speak, decision logs can be immensely useful as the membership of the community involved changes over time and ideas which appear to be new can be related to previous ideas and the decisions made about them. As with all project management tools there's a time investment required to maintain them, and that has to be weighed against the time investment required to rehash the discussions multiple times. In this case multiple people spent (presumably) hours of their time tracking down evidence of previous discussions, with good effect, and it appears that the new wiki content will be moving in the direction of a 'decision log'. This is good news :-)

"Real" anonymous functions for Python

Posted Mar 22, 2024 11:12 UTC (Fri) by smitty_one_each (subscriber, #28989) [Link]

> doesn't keep records of decisions *not* to do a thing

Maybe a "boneyard" PEP indexing the topics and discussions that the Good Idea Fairy left on doorstep, but later met a rhetorical demise.


Copyright © 2024, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds