python方法如何自动接收'self'作为第一个参数?

时间:2011-07-11 13:35:27

标签: python oop methods self

考虑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(函数)上缺少该属性是否会导致这种行为差异?这一切如何在内部发挥作用?

4 个答案:

答案 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.executes0.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>

绑定方法是一个可调用对象,除了传递调用它的所有参数外,还调用一个函数传递一个实例作为第一个参数。

请参阅:Python: Bind an Unbound Method?