为什么要将函数存储在python字典中?


68

我是python的初学者,我刚刚学习了一种涉及字典和函数的技术。语法很简单,看似微不足道,但是我的python感觉有点刺痛。有人告诉我这是一个深层的,非常Python化的概念,我不太了解它的重要性。有人可以给这项技术起个名字,并解释它为何/为什么有用吗?


该技术是当您拥有python字典和打算在其上使用的函数时。您在dict中插入一个额外的元素,其值是函数的名称。当您准备调用该函数时,可以通过引用dict元素而不是按名称引用该函数来间接发出该调用。

我正在使用的示例来自第2版的《学习Python的艰难方法》。(这是您通过Udemy.com注册时可用的版本;可惜的是,实时免费HTML版本当前是Ed 3,并且不再包含此示例)。

释义:

# 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

那么以下表达式是等效的。您可以直接调用该函数,也可以引用其值为该函数的dict元素。

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

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

有人可以解释这是什么语言功能,也许可以解释“真实”编程中的功能?这个玩具练习足以教给我语法,但并没有带我到那里。


13
为什么这篇文章会偏离主题?这是一个很棒的算法和数据结构概念问题!
马丁·彼得斯

我已经看到(并完成)了其他语言中的类似内容。您可以将其视为switch语句,但可以将其很好地包装在具有O(1)查找时间的可传递对象中。
KChaloux

1
我有一种预感,就是在将函数包含在其自己的字典中时有一些重要且具有自我参考意义的东西。
mdeutschmtl

Answers:


83

使用字典让我们将键转换为可调用键。但是,就像您的示例中一样,不需要对密钥进行硬编码。

通常,这是调用程序分派的一种形式,您可以在其中使用变量的值连接到函数。假设网络进程向您发送了命令代码,调度映射使您可以轻松地将命令代码转换为可执行代码:

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

请注意,我们现在调用的功能完全取决于的值command。密钥也不必匹配。它甚至不必是字符串,您可以使用任何可以用作键并适合您的特定应用程序的东西。

与其他技术(例如)相比,使用调度方法更为安全eval(),因为它将允许的命令限制为您事先定义的命令。例如,没有攻击者会偷偷ls)"; DROP TABLE Students; --经过调度表进行注入。


5
@Martjin-在这种情况下,这不能称为“命令模式”的实现吗?似乎是OP试图掌握的概念?
博士

3
@PhD:是的,我构建的示例是命令模式实现;的dict充当调度器(命令管理器,调用等)。
马丁·皮特斯

很棒的顶级解释,@ Martijn,谢谢。我想我明白了“派遣”的想法。
mdeutschmtl

28

@Martijn Pieters很好地解释了该技术,但我想澄清一下您的问题。

要知道的重要一点是,您不在字典中存储“函数的名称”。您正在存储对该函数本身的引用。您可以使用print功能上的看到此内容。

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

f只是引用您定义的函数的变量。使用字典可以对类似的东西进行分组,但是与将函数分配给不同的变量没有什么不同。

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

同样,您可以将函数作为参数传递。

>>> def c(func):
...   func()
... 
>>> c(f)
1

5
提到一流的功能肯定会有所帮助:-)
Florian Margaine 2013年

7

请注意,Python类实际上只是字典的语法糖。当您这样做时:

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

你打电话时

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

实际上与以下内容相同:

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

经过名称解析后,它与以下内容相同:

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

一种有用的技术是将用户输入映射到回调。例如:

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

也可以在课堂上写:

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

哪种回调语法更好,取决于特定的应用程序和程序员的喜好。前者是更实用的样式,后者是更面向对象的。如果您需要动态修改功能字典中的条目(可能基于用户输入),则前者可能会更自然。如果您具有一组可以动态选择的不同预设映射,则后者可能会更自然。


我认为这种可互换性使我感到“蟒蛇般的”,这是您在表面上看到的东西只是呈现更深层次内容的常规方式。尽管python练习(和python程序员?)似乎确实以这种方式谈论了很多关于语言功能的知识,但这并不是python特有的。
mdeutschmtl

另一个想法是,python是否有某种特定的方式愿意评估看起来像是两个相邻的两个“术语”,dict引用和参数列表的方式?其他语言允许吗?这种编程相当于代数从5 * x到的飞跃5x(原谅简单的类比)。
mdeutschmtl

@mdeutschmtl:它并不是Python真正独特的,尽管缺少一流函数或函数对象的语言可能永远不会出现在字典访问后立即调用函数的情况。
Lie Ryan

2
@mdeutschmtl“事实是,您在表面上看到的只是呈现更深层次内容的常规方式。” -这就是所谓的语法糖,并且存在于各处
Izkata 2014年

6

您可能会想到有两种让您想到的技术,它们都不是Python语言,因为它们比一种语言更广泛。

1.信息隐藏/封装和凝聚力技术(它们通常是并行的,因此我将它们组合在一起)。

您有一个包含数据的对象,并附加了一个与数据紧密结合的方法(行为)。如果您需要更改功能,扩展功能或进行其他任何更改,则调用者将不需要更改(假设不需要传入其他数据)。

2.调度表

不是经典的情况,因为只有一个条目具有一个功能。但是,调度表用于通过键来组织不同的行为,以便可以查询和动态调用它们。我不确定您是否考虑过这一点,因为您不是以动态方式引用该函数,但是尽管如此,您仍然可以获得有效的后期绑定(“间接”调用)。

权衡

要注意的一件事是,使用已知的键命名空间,您的工作将会很好。但是,如果键的名称空间未知,则存在数据与函数之间发生冲突的风险。


封装是因为数据和存储在dict(ionary)中的函数是相关的,因此具有内聚性。数据和函数来自两个截然不同的域,因此乍一看,该示例似乎将完全不同的实体组合在一起。
ChuckCottrill 2014年

0

我发布了这个我认为很通用的解决方案,因为它简单易用,可以适应特定情况,因此很有用:

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)

也可以定义一个列表,其中每个元素都是一个功能对象,并使用__call__内置方法。归功于所有人的启发与合作。

“伟大的艺术家就是简化者”,亨利·弗雷德里克·阿米尔Henri Frederic Amiel)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.