I’m a python beginner, and I just learned a technique involving dictionaries and functions. The syntax is easy and it seems like a trivial thing, but my python senses are tingling. Something tells me this is a deep and very pythonic concept and I’m not quite grasping its importance. Can someone put a name to this technique and explain how/why it’s useful?
The technique is when you have a python dictionary and a function that you intend to use on it. You insert an extra element into the dict, whose value is the name of the function. When you’re ready to call the function you issue the call indirectly by referring to the dict element, not the function by name.
The example I’m working from is from Learn Python the Hard Way, 2nd Ed. (This is the version available when you sign up through Udemy.com; sadly the live free HTML version is currently Ed 3, and no longer includes this example).
To paraphrase:
# make a dictionary of US states and major cities
cities = {'San Diego':'CA', 'New York':'NY', 'Detroit':'MI'}
# define a function to use on such a dictionary
def find_city (map, city):
# does something, returns some value
if city in map:
return map[city]
else:
return "Not found"
# then add a final dict element that refers to the function
cities['_found'] = find_city
Then the following expressions are equivalent. You can call the function directly, or by referencing the dict element whose value is the function.
>>> find_city (cities, 'New York')
NY
>>> cities['_found'](cities, 'New York')
NY
Can someone explain what language feature this is, and maybe where it comes to play in “real” programming? This toy exercise was enough to teach me the syntax, but didn’t take me all the way there.
3
Using a dict let’s you translate the key into a callable. The key doesn’t need to be hardcoded though, as in your example.
Usually, this is a form of caller dispatch, where you use the value of a variable to connect to a function. Say a network process sends you command codes, a dispatch mapping lets you translate the command codes easily into executable code:
def do_ping(self, arg):
return 'Pong, {0}!'.format(arg)
def do_ls(self, arg):
return 'n'.join(os.listdir(arg))
dispatch = {
'ping': do_ping,
'ls': do_ls,
}
def process_network_command(command, arg):
send(dispatch[command](arg))
Note that what function we call now depends entirely on what the value is of command
. The key doesn’t have to match either; it doesn’t even have to be a string, you could use anything that can be used as a key, and fits your specific application.
Using a dispatch method is safer than other techniques, such as eval()
, as it limits the commands allowable to what you defined beforehand. No attacker is going to sneak a ls)"; DROP TABLE Students; --
injection past a dispatch table, for example.
5
@Martijn Pieters did a good job explaining the technique, but I wanted to clarify something from your question.
The important thing to know is that you are NOT storing “the name of the function” in the dictionary. You are storing a reference to the function itself. You can see this using a print
on the function.
>>> def f():
... print 1
...
>>> print f
<function f at 0xb721c1b4>
f
is just a variable that references the function you defined. Using a dictionary allows you to group like things, but it isn’t any different from assigning a function to a different variable.
>>> a = f
>>> a
<function f at 0xb721c3ac>
>>> a()
1
Similarly, you can pass a function as an argument.
>>> def c(func):
... func()
...
>>> c(f)
1
1
Note that Python class is really just a syntax sugar for dictionary. When you do:
class Foo(object):
def find_city(self, city):
...
when you call
f = Foo()
f.find_city('bar')
is really just the same as:
getattr(f, 'find_city')('bar')
which, after name resolution, is just the same as:
f.__class__.__dict__['find_city'](f, 'bar')
One useful technique is for mapping user input to callbacks. For example:
def cb1(...):
...
funcs = {
'cb1': cb1,
...
}
while True:
input = raw_input()
funcs[input]()
This can alternatively be written in class:
class Funcs(object):
def cb1(self, a):
...
funcs = Funcs()
while True:
input = raw_input()
getattr(funcs, input)()
whichever callback syntax is better depends on the particular application and programmer’s taste. The former is more functional style, the latter is more object oriented. The former might feel more natural if you need to modify the entries in the function dictionary dynamically (perhaps based on user input); the latter might feel more natural if you have a set of different presets mapping that can be chosen dynamically.
4
There are 2 techniques that leap to my mind which you may be referring to, neither of which are Pythonic in that they are more broad than one language.
1. The technique of information hiding/encapsulation and cohesion (they usually go hand in hand so I’m lumping them together).
You have an object which has data, and you attaching a method (behavior) which is highly cohesive with the data. Should you need to change the function, extend the functionality or make any other changes the callers will not need to change (assuming no additional data needs to be passed in).
2. Dispatch tables
Not the classic case, because there is only one entry with a function. However, dispatch tables are used to organize different behaviors by a key such that they can looked up and called dynamically. I’m not sure if your thinking of this since your not referring to the function in a dynamic way, but none the less you still gain effective late binding (the “indirect” call).
Tradeoffs
One thing to note is what your doing will work fine with a known namespace of keys. However, you run the risk of collision between the data and the functions with an unknown namespace of keys.
1
I post this solution that I think is quite generic and can be useful as it is kept simple and easy to adapt to specific cases:
def p(what):
print 'playing', cmpsr
def l(what):
print 'listening', cmpsr
actions = {'Play' : p, 'Listen' : l}
act = 'Listen'
cmpsr = 'Vivaldi'
actions[act].__call__(cmpsr)
one can also define a list where each element is a function object and use the __call__
built in method.
Credits to all for the inspiration and cooperation.
“The great artist is the simplifier”, Henri Frederic Amiel