考虑Python中的策略模式示例(改编自示例here)。在这种情况下,备用策略是一种功能。
class StrategyExample(object):
def __init__(self, strategy=None) :
if strategy:
self.execute = strategy
def execute(*args):
# I know that the first argument for a method
# must be 'self'. This is just for the sake of
# demonstration
print locals()
#alternate strategy is a function
def alt_strategy(*args):
print locals()
以下是默认策略的结果。
>>> s0 = StrategyExample()
>>> print s0
<__main__.StrategyExample object at 0x100460d90>
>>> s0.execute()
{'args': (<__main__.StrategyExample object at 0x100460d90>,)}
在上面的示例中,s0.execute
是一个方法(不是普通的vanilla函数),因此args
中的第一个参数(正如预期的那样)是self
。
以下是备用策略的结果。
>>> s1 = StrategyExample(alt_strategy)
>>> s1.execute()
{'args': ()}
在这种情况下,s1.execute
是一个简单的vanilla函数,正如预期的那样,不会收到self
。因此args
为空。等一下!这是怎么发生的?
方法和函数都以相同的方式调用。方法如何自动获取self
作为第一个参数?当一个方法被普通的vanilla函数替换时,不如何将self
作为第一个参数?
我能找到的唯一区别是我检查了默认策略和备用策略的属性。
>>> print dir(s0.execute)
['__cmp__', '__func__', '__self__', ...]
>>> print dir(s1.execute)
# does not have __self__ attribute
在__self__
(方法)上是否存在s0.execute
属性,但在s1.execute
(函数)上缺少该属性是否会导致这种行为差异?这一切如何在内部发挥作用?
答案 0 :(得分:8)
您需要将未绑定的方法(即带有self
参数)分配给类或绑定到对象的方法。
通过descriptor mechanism,您可以创建自己的绑定方法,这也是为什么它在将(未绑定)函数分配给类时的原因:
my_instance = MyClass()
MyClass.my_method = my_method
当调用my_instance.my_method()
时,查找将找不到my_instance
上的条目,这就是为什么它稍后会这样做:MyClass.my_method.__get__(my_instance, MyClass)
- 这是描述符协议。这将返回一个绑定到my_instance的新方法,然后在属性之后使用()
运算符执行该方法。
这将在MyClass的所有实例之间共享方法,无论它们何时被创建。但是,他们可以隐藏&#34;分配该属性之前的方法。
如果您只希望特定对象拥有该方法,只需手动创建绑定方法:
my_instance.my_method = my_method.__get__(my_instance, MyClass)
有关描述符(指南)的更多详细信息,请参阅here。
答案 1 :(得分:6)
该方法是函数的包装器,并以实例作为第一个参数调用该函数。是的,它包含__self__
属性(在3.x之前的Python中也是im_self
),用于跟踪它所连接的实例。但是,将该属性添加到普通函数不会使其成为一种方法;你需要添加包装器。 Here is how(尽管您可能希望使用MethodType
模块中的types
来获取构造函数,而不是使用type(some_obj.some_method)
。
顺便说一下,函数包装可以通过方法的__func__
(或im_func
)属性访问。
答案 2 :(得分:5)
您可以在“用户定义的方法”下的python参考中阅读完整的解释here。可以在python教程的method objects描述中找到更简单,更简单的解释:
如果您仍然不了解方法的工作原理,那么查看实现可能会澄清问题。当引用不是数据属性的实例属性时,将搜索其类。如果名称表示作为函数对象的有效类属性,则通过打包(指向)实例对象和刚在抽象对象中找到的函数对象来创建方法对象:这是方法对象。当使用参数列表调用方法对象时,将从实例对象和参数列表构造新的参数列表,并使用此新参数列表调用函数对象。
基本上,你的例子中会发生这样的事情:
StrategyExample.execute
你得到一个“未绑定的方法”:它不“知道”它“属于”哪个实例,所以如果你想在实例上使用它,你需要提供实例作为第一个参数你自己,比如。 StrategyExample.execute(s0)
self.execute
或s0.execute
,你得到一个“绑定方法”:它“知道”它“属于”哪个对象,并以实例作为第一个参数进行调用。self.execute = strategy
或甚至s0.execute = strategy
中只是一个普通函数(与方法相反,它不会通过类)要使您的示例在两种情况下都能正常工作:
要么将函数转换为“真实”方法:可以使用types.MethodType
执行此操作:
self.execute = types.MethodType(strategy, self, StrategyExample)
(您或多或少告诉班级,当{1}}被要求使用此特定实例时,应将execute
转换为绑定方法
或者 - 如果您的策略实际上不需要访问实例 - 您可以反过来将原始的strategy
方法转换为静态方法(再次使其成为正常函数:不会将实例作为第一个参数调用,因此execute
将与s0.execute()
完全相同):
StrategyExample.execute()
答案 3 :(得分:1)
执行self.execute = strategy
时,将属性设置为普通方法:
>>> s = StrategyExample()
>>> s.execute
<bound method StrategyExample.execute of <__main__.StrategyExample object at 0x1dbbb50>>
>>> s2 = StrategyExample(alt_strategy)
>>> s2.execute
<function alt_strategy at 0x1dc1848>
绑定方法是一个可调用对象,除了传递调用它的所有参数外,还调用一个函数传递一个实例作为第一个参数。