SyntaxError: Generator expression must be parenthesized if not sole argument

1

I would like to combine a de-structuring with a python map to process pairs of columns in a data list.

My ideal solution would be something like

def openOrSenior(data):
  return list(map(lambda [age, handicap]: 'Senior' if (age>54 and handicap>7) else 'Open', data))

The solution to the problem without deconstruction that I have is

def openOrSenior(data):
    return list(map(lambda arr: 'Senior' if (arr[0]>54 and arr[1]>7) else 'Open', data))

To achieve this I came up with this:

def openOrSenior(data):
  return list(map(lambda arr: 'Senior' if (age>54 and handicap>7) else 'Open' for (age, handicap) in arr, data))

but I get an error that I can not understand.

  

SyntaxError: Generator expression must be parenthesized if not sole argument

Thanks to abulafia the error was corrected but it still seems like a waste compared to my ideal solution.

def openOrSenior(data):
  return sum(list(map(lambda arr: list('Senior' if (age>54 and handicap>7) else 'Open' for (age, handicap) in [arr]), data)),[])

before this I have the following questions: Why does a generator detect me if I do not have yield return ? Is there a way to achieve my ideal solution?

    
asked by Ruslan López 02.03.2018 в 15:21
source

1 answer

1

A generator can be created using a function that contains a yield , but can also be returned by other standard python functions such as range() , map() and others.

Also (and this is relevant here) by what is called a "generating expression" ( generator expression ), which are similar to the list comprehensions but with parentheses instead of brackets. For example:

>>> numeros = range(10)                   # numeros es un generador
>>> cuadrados = (x**2 for x in numeros)   # cuadrados es otro generador

None of the generators has produced any data yet. They will begin to produce them when we evaluate them in a loop, for example:

>>> for x in cuadrados:
...   pass

Then the generator cuadrados will be executed, and that will also cause the execution of the generator numeros (which in turn causes the execution of the generator returned by range() .

A function can receive a generator as a parameter. For example, the sum() function iterates over the elements returned by the generator and adds them all. So:

>>> sum(cuadrados)
285

Once the generator has been "depleted", it can not be used again (it does not "contain" more elements)

>>> sum(cuadrados)
0

To the function that admits a generator as a parameter, we can pass the generating expression to it, as here:

>>> sum( (x**2 for x in range(10)) )
285

Look at the double parenthesis. We have parentheses to delimit the argument of sum() and others to delimit the generating expression. When there is no possible ambiguity (that is, when the generating expression is the only argument I am passing to it), these internal parentheses can be omitted. So:

>>> sum(x**2 for x in range(10))
285

However, if the function supports more than one parameter and we want to pass both, I can no longer omit the parentheses around the generating function. For example, sum() supports a second optional parameter with the initial value of the sum. Then I would call it like this:

>>> sum((x**2 for x in range(10)), 100)
385

If I omit the parentheses in the generating expression, I get an exception:

>>> sum(x**2 for x in range(10), 100)
  File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument

Does this error message ring? Now you know what you had wrong ;-)

Update

Seeking to approach the "ideal solution" sought by the user, and taking into account that Python 3 has eliminated the unpacking of the arguments of a lambda (also known as destructuration ), the closest would be the following:

def openOrSenior(data):
  return list(map(lambda p: (
                  lambda age, handicap: 'Senior' if (age>54 and handicap>7) else 'Open'
                  )(*p), data))

As you can see, the trick is to enclose your ideal lambda in another lambda that receives a single parameter p , and which I immediately call spending *p . That asterisk causes the expansion of the tuple p , and what reaches the lambda internal is the two values you were looking for.

It is still not the ideal solution, but for my taste it is better than the "waste" that the internal generator supposes and the sum later.

In any case, python has gradually fled this type of syntax, in favor of list comprehensions . Using one of these your function would look like this:

def openOrSenior(data):
    return ['Senior' if (age>54 and handicap>7) else 'Open'
             for (age, handicap) in data]

Infinitely more readable for my taste.

    
answered by 02.03.2018 / 16:28
source