Syntax for one-line "nonymous" functions in "declaration style"

Terry Reedy tjreedy at udel.edu
Wed Mar 27 17:25:35 EDT 2019


On 3/27/2019 4:21 AM, Alexey Muranov wrote:
> Whey you need a simple function in Python, there is a choice between a 
> normal function declaration and an assignment of a anonymous function 
> (defined by a lambda-expression) to a variable:
> 
>     def f(x): return x*x
> 
> or
> 
>     f = lambda x: x*x

PEP 8 properly recommends against this the latter as functionally it has 
no advantage and the disadvantage that f.__name__ becomes the generic 
'<lambda>' instead of the specific 'f'.  This is not useful for code 
that expects specific names.  Tracebacks are one example.  Here is 
another intended to list callable objects and their types.

def call_clas(container):
     for item in vars(container).values():
         if hasattr(item, '__call__'):
             yield item.__name__, item.__class__

def print_cc(container):
     for name, clas in call_clas(container):
         print(f'{name:30s}{clas}')

# Examples.
print_cc(int)
from idlelib import pyshell
print_cc(pyshell)

Multiple output lines of '<lambda>               function' defeat the 
purpose.

So my opinion is that lambda expressions should only be used within 
larger expressions and never directly bound.

> It would be however more convenient to be able to write instead just
> 
>     f(x) = x*x

Given my view above, this is, standing alone, strictly an abbreviation 
of the equivalent def statement.  I am presuming that a proper 
implementation would result in f.__name__ == 'f'.

Is the convenience and (very low) frequency of applicability worth the 
inconvenience of confusing the meaning of '=' and complicating the 
implementation?

> I do not see any conflicts with the existing syntax.

It heavily conflicts with existing syntax.  The current meaning of
   target_expression = object_expression
is
1. Evaluate object_expression in the existing namespace to an object, 
prior to any new bindings and independent of the target_expression.
2. Evaluate target_expression in the existing namespace to one or more 
targets.
3. Bind object to target or iterate target to bind to multiple targets.

Part of step 2 is making calls, because calls cannot be targets.  This 
is why 'f(a+b)[c] = d' can work.  Note that 'a+b' makes calls to 
a.__add__ or b.__radd__ or both.

In the proposal, the treatment of the object expression would depend on 
the target expression and the alternative would be to quote it as code; 
compile the code in a manner that depends on the target expression to 
mark local names versus global names; and finally make a function 
instance, presumably taking __name__ from the target.

This would have the advantage over lambda assignment of getting the name 
right, but I don't think one should be doing lambda assignment anyway. 
There is a good reason to have a separate syntax.

Before 3.8, I would stop here and say no to the proposal.  But we now 
have assignment expressions in addition to assignment statements.

 >>> int(s:='42'+'742')
42742
 >>> s
'42742'

To me, function assignment expressions, as a enhanced replacement for 
lambda expressions, is more inviting than function assignment statements 
as an abbreviation for function definition statements.

In other words, replace

   map(lambda x: x*x, range(10))

with

   map(square(x):=x*x, range(10))

or, if one does not want a specific name,

   map(_(x):=x*x, range(10))

Many people dislike lambda expressions, to the point that Guido 
considered leaving them out of 3.x.  So this replacement might get more 
traction.  It would make assignment expressions much more useful.

-- 
Terry Jan Reedy





More information about the Python-list mailing list