Question

From what I understand, Python lambda's only allow expressions, not statements. I have a case where I an using len() in a lambda expression and am trying to get the length of the return value of a function call. However, that function call has the potential to return None, which breaks len(). Is there a graceful way around this?

Example:

def foo(obj_list, field):
    return maxk(obj_list, key=lambda obj: len(nested_getattr(obj, field)))

In the above, maxk() is a version of max() that accepts a key argument. I'm on Python 2.4 (and cannot use anything higher at present), so I have a custom implementation of max() that takes a key argument, sourced from here (refer to posts #140122 and #140143). nested_getattr() is another utility function that acts like getattr() but can fetch an attribute nested within another. That can be sourced from here.

What the example function does (this is the actual function I am using with variables/names changed) is go through a list of objects (obj_list) and compare the length of the value in field and returns the object in the list whose field is the largest overall.

However, if the attribute specified in field for each object returns None, then len() will choke with a TypeError. I think I could get around this using an inline conditional, but I would have to call nested_getattr() twice, once for the check, and possibly a second time if its return value is not None. I would rather like to cache its return value to a variable and run that on a conditional before deciding what to return (or just have the generator expression skip over it entirely).

What's a good way to handle this? I am open to other implementations of this function, maxk(), or nested_getattr() (if need be).

Était-ce utile?

La solution

Just define the function separately as a regular function and then pass it into the maxk:

def getLen(obj):
    val = nested_getattr(obj, field)
    if val is None:
        return 0
    else:
        return len(val)
return maxk(obj, key=getLen)

Lambdas are meant to be used when it is easier to write an inline single-expression function that it would be to write a separate function. If it becomes harder to think of how to write the lambda than to just write a normal function, just write a normal function. There's no point spending time how to fit it into a lambda, because the lambda doesn't gain you anything in that case.

Edit: Lambdas are bytecode-identical to non-lambda functions that do the same thing.

>>> f = lambda x: x**2
>>> def g(x):
...     return x**2
>>> import dis
>>> dis.dis(f)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (2)
              6 BINARY_POWER        
              7 RETURN_VALUE        
>>> dis.dis(g)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (2)
              6 BINARY_POWER        
              7 RETURN_VALUE     

Autres conseils

Maybe this:

lambda val: len(val or [])

You could add the third argument to nested_getattr() with the same meaning as the third argument of getattr():

len(nested_getattr(obj, field, ''))  

Also in this particular case len(nested_getattr(obj, field) or '')) (suggested by @DSM) works fine if possible returned values are sequences and None.

In general a named function such as suggested by @BrenBarn might be preferable.

def foo(obj_list, field):
    return maxk(obj_list, key=lambda obj: len(nested_getattr(obj, field) or []))

I can't test on 2.4 right now, but this works in 3.2.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top