Unpacking Ad-hoc Dictionaries

2013-01-27 8:04

Today I’d like to present something what I consider rather obvious. Normally I don’t do that, but I’ve had one aspiring Pythonist whom I helped with the trick below, and he marveled at the apparent cleverness of this solution. So I thought it might be useful for someone else, too.

Here’s the deal. In Python, functions can be invoked with keyword arguments, so that argument name appears in the function call. Many good APIs use that feature extensively; database libraries known as ORMs are one typical example:

  1. user = session.query(User).filter_by(email="joe@example.com").first()

In this call to filter_by() we pass the email argument as a keyword. Its value is then used to construct an SQL query that contains a filter on email column in the WHERE clause. By adding more arguments, we can introduce more filters, linked together with the AND operator.

Suppose, though, that we don’t know the column name beforehand. We just have it stored in some variable, maybe because the query is part of authentication procedure and we support different means for it: e-mail, Facebook user ID, Twitter handle, etc.
However, the keyword arguments in function call must always be written as literal Python identifiers. Which means that we would need to “eval” them somehow, i.e. compute dynamically.

How? Probably best is to construct an ad-hoc dictionary and unpack it with ** operator:

  1. def get_user(column_name, column_value):
  2.     return session.query(User).filter(**{column_name: column_value}).first()

That’s it. It may not be obvious at first, because normally we only unpack dictionaries that were carefully crafted as local variables, or received as kwargs parameters:

  1. def some_function(one_arg, **kwargs):
  2.     kwargs['foo'] = 'bar'
  3.     some_other_function(**kwargs)

But ** works on any dictionary. We are thus perfectly allowed to create one and then unpack it immediately. It doesn’t make much sense in most cases, but this is one of the two instances when it does.

The other situation arises when we know the argument name while writing code, but we cannot use it directly. Python reserves many short, common words with plethora of meanings (computer-scientific or otherwise), so this is not exactly a rare occurrence. You may encounter it when building URLs in Flask:

  1. login_url = url_for('login', **{'as': test_user_id})

or parsing HTML with BeautifulSoup:

  1. comment_spans = comments_table.find_all('span', **{'class': 'comment'})

Strictly speaking, this technique allows you to have completely arbitrary argument names which are not even words. Special handling would be required on both ends of function call, though.

Be Sociable, Share!
Be Sociable, Share!
Tags: , ,
Author: Xion, posted under Computer Science & IT »


2 comments for post “Unpacking Ad-hoc Dictionaries”.
  1. Kos:
    January 27th, 2013 o 15:36

    BeautifulSoup is nice enough to also accept class_ as an alias for class in find_all.

  2. Xion:
    January 31st, 2013 o 2:03

    I discovered you can actually just pass a dictionary of attributes, omitting ** from the example I posted. Oh well, the other case – and likely much more out in the wild – still does apply.

Comments are disabled.
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.