Por que armazenar uma função dentro de um dicionário python?

65

Sou um iniciante em Python e acabei de aprender uma técnica envolvendo dicionários e funções. A sintaxe é fácil e parece uma coisa trivial, mas meus sentidos python estão formigando. Algo me diz que este é um conceito profundo e muito pythonic e eu não estou entendendo bem a sua importância. Alguém pode colocar um nome para esta técnica e explicar como / porque é útil?

A técnica é quando você tem um dicionário python e uma função que você pretende usar nele. Você insere um elemento extra no dict, cujo valor é o nome da função. Quando você estiver pronto para chamar a função, você emite a chamada indiretamente, referindo-se ao elemento dict, não à função por nome.

O exemplo do qual estou trabalhando é da Learn Python the Hard Way, 2ª Ed. (Esta é a versão disponível quando você se inscrever através de Udemy.com ; infelizmente, o viver versão HTML gratuita é atualmente Ed 3, e não inclui mais este exemplo).

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

Em seguida, as seguintes expressões são equivalentes. Você pode chamar a função diretamente ou referenciando o elemento dict cujo valor é a função.

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

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

Alguém pode explicar qual é o recurso de linguagem que é e, talvez, onde se trata de programação "real"? Este exercício de brinquedo foi o suficiente para me ensinar a sintaxe, mas não me levou até lá.

    
por mdeutschmtl 09.01.2013 / 21:10
fonte

5 respostas

75

Usando um dict, você pode traduzir a chave em um callable. A chave não precisa ser codificada, como no seu exemplo.

Normalmente, esta é uma forma de envio de chamadas, onde você usa o valor de uma variável para se conectar a uma função. Digamos que um processo de rede lhe envie códigos de comando, um mapeamento de despacho permite traduzir os códigos de comando facilmente em código executável:

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 que a função que chamamos agora depende inteiramente do valor de command . A chave também não precisa corresponder; nem precisa ser uma string, você pode usar qualquer coisa que possa ser usada como chave e se adequar à sua aplicação específica.

O uso de um método de despacho é mais seguro do que outras técnicas, como eval() , pois limita os comandos permitidos ao que você definiu anteriormente. Nenhum invasor vai esgueirar uma injeção de ls)"; DROP TABLE Students; -- após uma tabela de despacho, por exemplo.

    
por 09.01.2013 / 21:13
fonte
27

@Martijn Pieters fez um bom trabalho explicando a técnica, mas eu queria esclarecer algo da sua pergunta.

O importante é saber que você está NÃO armazenando "o nome da função" no dicionário. Você está armazenando uma referência à função em si. Você pode ver isso usando um print na função.

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

f é apenas uma variável que faz referência à função que você definiu. Usar um dicionário permite agrupar coisas semelhantes, mas não é diferente de atribuir uma função a uma variável diferente.

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

Da mesma forma, você pode passar uma função como argumento.

>>> def c(func):
...   func()
... 
>>> c(f)
1
    
por 09.01.2013 / 21:25
fonte
7

Note que a classe Python é realmente apenas um açúcar de sintaxe para o dicionário. Quando você faz:

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

quando você liga

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

é realmente o mesmo que:

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

que, após a resolução de nomes, é igual a:

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

Uma técnica útil é mapear a entrada do usuário para retornos de chamada. Por exemplo:

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

Como alternativa, isso pode ser escrito em sala de aula:

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

qualquer sintaxe de retorno de chamada é melhor depende do aplicativo específico e do gosto do programador. O primeiro é um estilo mais funcional, o segundo é mais orientado a objetos. O primeiro pode parecer mais natural se você precisar modificar as entradas no dicionário de funções dinamicamente (talvez com base na entrada do usuário); o último pode parecer mais natural se você tiver um conjunto de mapeamentos diferentes que podem ser escolhidos dinamicamente.

    
por 10.01.2013 / 04:23
fonte
6

Existem 2 técnicas que me vêm à mente e que você pode estar se referindo, e nenhuma delas é Pythonica, pois são mais amplas que uma língua.

1. A técnica de esconder informações / encapsulamento e coesão (eles geralmente andam de mãos dadas, então eu os agregue).

Você tem um objeto que possui dados e anexa um método (comportamento) altamente coeso aos dados. Se você precisar alterar a função, estender a funcionalidade ou fazer quaisquer outras alterações, os chamadores não precisarão ser alterados (supondo que nenhum dado adicional precise ser passado).

2. Tabelas de despacho

Não é o caso clássico, porque existe apenas uma entrada com uma função. No entanto, as tabelas de despacho são usadas para organizar diferentes comportamentos por uma chave, de modo que possam ser consultadas e chamadas dinamicamente. Não tenho certeza se você está pensando nisso, já que não está se referindo à função de maneira dinâmica, mas, no entanto, ainda ganha ligação efetiva (chamada "indireta").

Tradeoffs

Uma coisa a notar é que o seu trabalho funcionará bem com um namespace de chaves conhecido. No entanto, você corre o risco de colisão entre os dados e as funções com um namespace de chaves desconhecido.

    
por 10.01.2013 / 02:34
fonte
0

Eu postei essa solução que acho bastante genérica e que pode ser útil, pois é simples e fácil de se adaptar 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)

também é possível definir uma lista em que cada elemento é um objeto de função e usar o método __call__ incorporado. Créditos para todos pela inspiração e cooperação.

"O grande artista é o simplificador", Henri Frederic Amiel

    
por 02.12.2016 / 16:07
fonte