¿Por qué almacenar una función dentro de un diccionario de python?

64

Soy un principiante de python, y acabo de aprender una técnica que incluye diccionarios y funciones. La sintaxis es fácil y parece algo trivial, pero mis sentidos python están hormigueando. Algo me dice que este es un concepto profundo y muy pitón y no estoy entendiendo su importancia. ¿Alguien puede ponerle un nombre a esta técnica y explicar por qué / por qué es útil?

La técnica es cuando tienes un diccionario de python y una función que pretendes usar en él. Usted inserta un elemento adicional en el dict, cuyo valor es el nombre de la función. Cuando esté listo para llamar a la función, emita la llamada indirectamente refiriéndose al elemento dict, no a la función por su nombre.

El ejemplo del que estoy trabajando es de Learn Python the Hard Way, 2nd Ed. (Esta es la versión disponible cuando te registras en Udemy.com ; por desgracia, la versión HTML gratuita en vivo actualmente es Ed 3, y ya no incluye este ejemplo).

Parafraseando:

# 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

Entonces las siguientes expresiones son equivalentes. Puede llamar a la función directamente, o haciendo referencia al elemento dict cuyo valor es la función.

>>> find_city (cities, 'New York')
NY

>>> cities['_found'](cities, 'New York')
NY

¿Alguien puede explicar qué característica de idioma es esta, y quizás dónde se juega en la programación "real"? Este ejercicio de juguete fue suficiente para enseñarme la sintaxis, pero no me llevó hasta allí.

    
pregunta mdeutschmtl 09.01.2013 - 21:10

5 respuestas

74

Usando un dict te permite traducir la clave en un llamable. Sin embargo, la clave no necesita estar codificada, como en su ejemplo.

Por lo general, esta es una forma de envío de la persona que llama, donde se usa el valor de una variable para conectarse a una función. Supongamos que un proceso de red le envía códigos de comando, una asignación de despacho le permite traducir los códigos de comando fácilmente en código ejecutable:

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))

Tenga en cuenta que la función a la que llamamos ahora depende completamente de cuál es el valor de command . La clave tampoco tiene que coincidir; ni siquiera tiene que ser una cadena, puede usar cualquier cosa que se pueda usar como clave y se adapte a su aplicación específica.

Utilizar un método de envío es más seguro que otras técnicas, como eval() , ya que limita los comandos permitidos a lo que definió de antemano. Ningún atacante va a pasar una inyección de ls)"; DROP TABLE Students; -- más allá de una tabla de despacho, por ejemplo.

    
respondido por el Martijn Pieters 09.01.2013 - 21:13
27

@Martijn Pieters hizo un buen trabajo explicando la técnica, pero quería aclarar algo de su pregunta.

Lo importante que hay que saber es que NO está almacenando "el nombre de la función" en el diccionario. Está almacenando una referencia a la función en sí. Puedes ver esto usando un print en la función.

>>> def f():
...   print 1
... 
>>> print f
<function f at 0xb721c1b4>

f es solo una variable que hace referencia a la función que definió. El uso de un diccionario le permite agrupar cosas similares, pero no es diferente de asignar una función a una variable diferente.

>>> a = f
>>> a
<function f at 0xb721c3ac>
>>> a()
1

Del mismo modo, puedes pasar una función como argumento.

>>> def c(func):
...   func()
... 
>>> c(f)
1
    
respondido por el unholysampler 09.01.2013 - 21:25
7

Tenga en cuenta que la clase Python es en realidad solo una sintaxis de azúcar para el diccionario. Cuando lo hagas:

class Foo(object):
    def find_city(self, city):
        ...

cuando llamas

f = Foo()
f.find_city('bar')

es realmente lo mismo que:

getattr(f, 'find_city')('bar')

que, después de la resolución de nombres, es lo mismo que:

f.__class__.__dict__['find_city'](f, 'bar')

Una técnica útil es para asignar la entrada del usuario a las devoluciones de llamada. Por ejemplo:

def cb1(...): 
    ...
funcs = {
    'cb1': cb1,
    ...
}
while True:
    input = raw_input()
    funcs[input]()

Esto también puede escribirse en clase:

class Funcs(object):
    def cb1(self, a): 
        ...
funcs = Funcs()
while True:
    input = raw_input()
    getattr(funcs, input)()

la sintaxis de devolución de llamada que sea mejor depende de la aplicación particular y del gusto del programador. El primero es un estilo más funcional, el segundo está más orientado a objetos. El primero puede parecer más natural si necesita modificar las entradas en el diccionario de funciones dinámicamente (quizás en función de la entrada del usuario); este último podría sentirse más natural si tiene un conjunto de diferentes asignaciones de ajustes preestablecidos que se pueden elegir dinámicamente.

    
respondido por el Lie Ryan 10.01.2013 - 04:23
6

Hay 2 técnicas que me vienen a la mente a las que te puedes referir, ninguna de las cuales es Pythonic, ya que son más amplias que un solo idioma.

1. La técnica de ocultación / encapsulación y cohesión de la información (por lo general van de la mano, así que los estoy agrupando).

Tienes un objeto que tiene datos y estás adjuntando un método (comportamiento) que es altamente cohesivo con los datos. En caso de que necesite cambiar la función, ampliar la funcionalidad o realizar cualquier otro cambio, los llamantes no tendrán que cambiar (suponiendo que no es necesario transmitir datos adicionales).

2. Mesas de despacho

No es el caso clásico, porque solo hay una entrada con una función. Sin embargo, las tablas de despacho se utilizan para organizar diferentes comportamientos mediante una clave de manera que se pueden buscar y llamar de forma dinámica. No estoy seguro de si está pensando en esto, ya que no se está refiriendo a la función de forma dinámica, pero no obstante, sigue obteniendo una vinculación tardía efectiva (la llamada "indirecta").

Tradeoffs

Una cosa a tener en cuenta es que lo que haces funcionará bien con un espacio de nombres conocido de claves. Sin embargo, corre el riesgo de colisión entre los datos y las funciones con un espacio de nombre de teclas desconocido.

    
respondido por el dietbuddha 10.01.2013 - 02:34
0

Publico esta solución que creo que es bastante genérica y puede ser útil, ya que se mantiene simple y se adapta a casos específicos:

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)

también se puede definir una lista donde cada elemento es un objeto de función y utilizar el método integrado __call__ . Créditos a todos por la inspiración y cooperación.

"El gran artista es el simplificador", Henri Frederic Amiel

    
respondido por el Emanuele 02.12.2016 - 16:07

Lea otras preguntas en las etiquetas