4

I am not very familiar with Python syntax, and was wondering if anyone can explain to me how the variable match is taking on a string found inside the for expression in this function:

def find_project(project_name):    
    projects = get_projects()    
    try:
        match, = (proj for proj in projects if proj["name"].strip() == project_name)
        return match
    except ValueError:
        return None
Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
j.doe
  • 87
  • 6

1 Answers1

7

Python allows you to assign more than one variable at once, like this:

a, b = 1, 2

The way this works is by treating the left hand side of the assignment a, b as a tuple, and assigning each element in the tuple 1, 2 on the right hand side to it.

Since tuples can have just one element, the following also works:

a, = 1,

The right hand side of a multiple assignment doesn't have to be a tuple. Any iterable will do, as long as the number of elements on each side is the same:

a, b, c = "three little pigs".split()

If the number of elements doesn't match:

a, b, c = "oh", "no"

... you get a ValueError:

ValueError: not enough values to unpack (expected 3, got 2)

Putting all of the above together, then, your function:

def find_project(project_name):    
    projects = get_projects()    
    try:
        match, = (proj for proj in projects if proj["name"].strip() == project_name)
        return match
    except ValueError:
        return None

... iterates over the generator expression

(proj for proj in projects if proj["name"].strip() == project_name)

... and if the result has one element, assigns that element to match. If not, ValueError is raised (and caught by the except clause), no assignment happens, and None is returned .

Two things to note:

  1. The comma , is easy to miss when reading code. An alternative would be to use list syntax on the left hand side:

    [match] = (proj for proj in projects if proj["name"].strip() == project_name)
    

    ... which has the same effect.

  2. When the right hand side is a generator expression (or some other kind of iterator), you can use next() with a default value instead:

    def find_project(project_name):    
        projects = get_projects()    
        it = (proj for proj in projects if proj["name"].strip() == project_name)
        return next(it, None)
    

    ... which is shorter and more readable.

wim
  • 338,267
  • 99
  • 616
  • 750
Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160