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

Pra*_*ota 11 python oop methods self

考虑一下Python中策略模式的这个例子(改编自这里的例子).在这种情况下,替代策略是一种功能.

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()
Run Code Online (Sandbox Code Playgroud)

以下是默认策略的结果.

>>> s0 = StrategyExample()
>>> print s0
<__main__.StrategyExample object at 0x100460d90>
>>> s0.execute()
{'args': (<__main__.StrategyExample object at 0x100460d90>,)}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中s0.execute是一个方法(不是普通的vanilla函数),因此args,正如预期的那样,第一个参数是self.

以下是替代策略的结果.

>>> s1 = StrategyExample(alt_strategy)
>>> s1.execute()
{'args': ()}
Run Code Online (Sandbox Code Playgroud)

在这种情况下s1.execute是普通的香草功能,并按预期,不接收self.因此args是空的.等一下!这怎么发生的?

方法和函数都以相同的方式调用.方法如何自动self作为第一个参数?当一个方法被一个普通的vanilla函数替换时,它怎么没有得到self第一个参数?

我能够找到的唯一区别是我检查了默认策略和备用策略的属性.

>>> print dir(s0.execute)
['__cmp__', '__func__', '__self__', ...]
>>> print dir(s1.execute)
# does not have __self__ attribute
Run Code Online (Sandbox Code Playgroud)

__self__属性s0.execute(方法)的存在,但缺乏它s1.execute(函数)会以某种方式解释这种行为差异吗?这一切在内部如何运作?

pha*_*t0m 8

您需要将未绑定的方法(即带self参数)分配给类或绑定到对象的方法.

通过描述符机制,您可以创建自己的绑定方法,这也是将(未绑定)函数分配给类时的原因:

my_instance = MyClass()    
MyClass.my_method = my_method
Run Code Online (Sandbox Code Playgroud)

在调用时my_instance.my_method(),查找将不会找到条目my_instance,这就是为什么它稍后将最终执行此操作:MyClass.my_method.__get__(my_instance, MyClass)- 这是描述符协议.这将返回一个绑定到my_instance的新方法,然后使用()该属性后面的运算符执行该方法.

这将在MyClass的所有实例之间共享方法,无论它们何时被创建.但是,在分配该属性之前,他们可以"隐藏"该方法.

如果您只希望特定对象具有该方法,则只需手动创建绑定方法:

my_instance.my_method = my_method.__get__(my_instance, MyClass)
Run Code Online (Sandbox Code Playgroud)

有关描述符(指南)的更多详细信息,请参见此处.


kin*_*all 6

该方法是函数的包装器,并以实例作为第一个参数调用该函数.是的,它包含一个__self__属性(也在im_self3.x之前的Python中),用于跟踪它所附加的实例.但是,将该属性添加到普通函数不会使其成为一种方法; 你需要添加包装器.这是如何(尽管您可能希望MethodTypetypes模块中使用获取构造函数,而不是使用type(some_obj.some_method).

顺便说一下,包装的函数可以通过方法的__func__(或im_func)属性访问.


Ste*_*ven 6

你可以阅读完整的说明这里的蟒蛇参考,在“用户定义的方法”。可以在python教程的方法对象说明中找到更简短的说明:

如果您仍然不了解方法的工作方式,那么看一下实现也许可以澄清问题。当引用的实例属性不是数据属性时,将搜索其类。如果名称表示作为函数对象的有效类属性,则通过将实例对象和刚在抽象对象中一起找到的函数对象打包(指向)来创建方法对象:这是方法对象。当使用实参列表调用方法对象时,将从实例对象和实参列表构造一个新的实参列表,并使用该新的实参列表来调用函数对象。

基本上,您的示例中发生的是:

  • 分配给类的函数(当您在类主体中声明方法时发生)是...方法。
    • 当您通过类访问该方法时,例如。StrategyExample.execute您将获得一个“未绑定方法”:它不“知道”该实例“属于”哪个实例,因此,如果要在实例上使用它,则需要自己提供实例作为第一个参数。StrategyExample.execute(s0)
    • 当您通过实例访问方法时,例如 self.executes0.execute,您将获得一个“绑定方法”:它“知道”它“属于”哪个对象,并将以该实例作为第一个参数被调用。
  • 但是,您直接分配给实例属性的函数,例如,self.execute = strategy甚至s0.execute = strategy是……只是一个普通函数(与方法相反,它不会通过类传递)

为了使您的示例在两种情况下均能正常工作:

  • 您可以将函数转换为“真实”方法:可以使用types.MethodType以下方法进行操作:

    self.execute = types.MethodType(strategy, self, StrategyExample)
    
    Run Code Online (Sandbox Code Playgroud)

    (您或多或少告诉类,当execute要求该特定实例时,它应该变成strategy绑定方法)

  • 或-如果您的策略确实不需要访问该实例-您可以采用另一种方法,将原始execute方法转换为静态方法(再次使其成为普通函数:将不会以实例作为第一个调用该方法参数,因此s0.execute()StrategyExample.execute())完全相同:

    @staticmethod
    def execute(*args):
        print locals()
    
    Run Code Online (Sandbox Code Playgroud)