【问题标题】:How does a python method automatically receive 'self' as the first argument?python方法如何自动接收'self'作为第一个参数?
【发布时间】:2011-10-02 19:33:48
【问题描述】:

考虑这个 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 是一个方法(不是一个普通的函数),因此args 中的第一个参数,正如预期的那样,是self

这是替代策略的结果。

>>> s1 = StrategyExample(alt_strategy)
>>> s1.execute()
{'args': ()}

在这种情况下,s1.execute 是一个普通的函数,正如预期的那样,它不会收到self。因此args 是空的。等一下!这是怎么发生的?

方法和函数都以相同的方式调用。方法如何自动获取self 作为第一个参数?当一个方法被一个普通的普通函数替换时,它如何self作为第一个参数?

我能找到的唯一区别是在检查默认策略和备用策略的属性时。

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

s0.execute(方法)上存在__self__ 属性,但s1.execute(函数)上没有它,是否以某种方式解释了这种行为差异?这一切在内部如何运作?

【问题讨论】:

  • 您可以将instance.method(arg) 视为InstanceClass.method(instance, arg) 的简写。 Python 试图让事情尽可能简单明了,我发现这是一种访问调用函数的实例的“透明”方式

标签: python oop methods self


【解决方案1】:

您需要为类分配未绑定的方法(即带有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 的所有实例之间共享方法,无论它们是何时创建的。但是,他们可能在您分配该属性之前“隐藏”了该方法。

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

my_instance.my_method = my_method.__get__(my_instance, MyClass)

有关描述符的更多详细信息(指南),请参阅here

【讨论】:

  • 有趣的是,唯一真正向您展示您可以制作绑定方法并试图解释它的答案没有得到一个赞成...
  • 描述符访问是一种我不知道的创建方法的方法。我一直都是使用实例方法构造函数来完成的。
  • @kindall:你的意思是你链接到的文章中显示的那样吗?您可以导入它,而不是通过type() 获取它:from types import MethodType
  • 是的,这就是我一直以来的做法。关于MethodType 的好点,我会将其添加到我的答案中。
  • @pahnt0m 您的回答清楚地说明了如何通过在类中查找方法来解决方法。谢谢!
【解决方案2】:

您可以在 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)
    

    (你或多或少告诉类,当execute 被要求提供这个特定实例时,它应该将strategy 转换为绑定方法)

  • 或者 - 如果你的策略并不真的需要访问实例 - 你反过来把原来的 execute 方法变成一个静态方法(让它再次成为一个正常的函数:它不会被调用将实例作为第一个参数,因此s0.execute() 将与StrategyExample.execute() 完全相同):

    @staticmethod
    def execute(*args):
        print locals()
    

【讨论】:

  • 非常明确的答案,而且直接来自 Python 教程!!!表明我应该再次重温本教程,以吸收我很久以前浏览过的更深层次的语言细节。非常感谢!
【解决方案3】:

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

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

【讨论】:

  • 非常有用的类比:“方法是函数的包装器......”。并感谢您的链接!
【解决方案4】:

当您执行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?

【讨论】:

  • 这个术语是你有一个未绑定的方法。如果你 /bind/ 方法(你可以这样做),它会再次隐式传递实例作为第一个参数。
  • 感谢您提供有关绑定未绑定方法的链接!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-15
  • 1970-01-01
  • 1970-01-01
  • 2015-09-30
  • 1970-01-01
  • 2012-12-28
相关资源
最近更新 更多