Search code examples
pythonpython-2.7try-catchvariable-assignmentiterable-unpacking

What is the comma doing in this assignment?


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

Solution

  • 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.