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


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

我正在工作的示例来自Hard 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


有人可以解释这是什么语言功能,以及它的用处吗?玩“真实”编程?这个玩具练习足以教我语法,但并没有带我去那里。

评论

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

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

我有一种预感,就是在将函数包含在其自己的字典中时有一些重要且具有自参考意义的东西……请参阅@dietbuddha的回答……但也许不是吗?

#1 楼

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

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

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; --注入。

评论


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

–博士
13年10月10日在2:03

@PhD:是的,我构建的示例是命令模式实现; dict充当调度程序(命令管理器,调用程序等)。

–马丁·彼得斯(Martijn Pieters)
2013年1月10日7:02

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

–mdeutschmtl
13年1月10日在22:25

可以在类中使用这种技术吗?例如,执行mydict = {'api':Api()}可以使用mydict ['api']。testmethod()调用Api的方法吗?我实际上正在考虑这个问题,因为这将允许我使用不同的基础类调用同一字典键。

–罗·贝林(Lo Bellin)
20 Dec 15 '14:21

@LoBellin:绝对。这里的Api()产生Api类的实例,并且只是另一个对象。您可以将其存储在字典中,mydict [“ api”]会像在其他引用中一样检索那里的引用。在这方面,api = Api(),api.testmethod()和mydict = {“ api”:Api()},mydict [“ api”]。testmethod()之间没有区别。

–马丁·彼得斯(Martijn Pieters)
20 Dec 17 '13:44

#2 楼

@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


评论


提到一流的功能肯定会有所帮助:-)

–弗洛里安人造黄油
2013年1月9日20:32

#3 楼

请注意,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
2013年1月10日22:20



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

–mdeutschmtl
13年1月10日在22:29

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

– Lie Ryan
13年1月11日在8:07

@mdeutschmtl“事实是,您在表面上看到的只是呈现更深层次内容的常规方式。” -称为句法糖,遍布各地

–伊兹卡塔
2014年5月13日下午3:35

#4 楼

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

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

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

2。调度表

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

权衡

要注意的一件事是,使用已知的键命名空间,您的工作将会很好。但是,冒着未知的键名称空间冒着数据与函数冲突的风险。

评论


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

– ChuckCottrill
2014年5月14日14:07



#5 楼

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

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)