Python 3:何时调用实例属性,就像它是一个实例方法一样?

时间:2012-07-31 21:00:35

标签: python python-3.x instance-methods

class C:
  def func(self, a):
    print(a)

c = C()
print(c.__dict__) # {}
c.func = c.func
# c.func is now an instance attribute, which hides instance method
print(c.__dict__) # {'func' : (bound method C.f of (__main.C object at ...))}
# how come it still works as if it's an instance method:
c.func(1) # prints 1

# with a regular function assigned to c.func, this won't happen:
def newfunc(self, a):
  print(a)
c.func = newfunc
c.func(1) # TypeError

调用c.func(1)神奇地将实例c添加为第一个参数的条件是什么?我认为只有在func被定义为类中的实例方法时才会发生这种情况。但是,当func只是一个实例属性时,似乎有时有效。

2 个答案:

答案 0 :(得分:3)

当您访问 c.func时,会发生与实例的绑定,而不是在您调用它时。执行c.func = c.func时,右侧的属性访问由类C处理,并对绑定方法进行评估。将其重新分配给c.func会将其分配给实例,但它已经是绑定方法,因此这不会改变行为。

在你的newfunc示例中,您刚刚定义了一个函数并对其进行了分配,因此它永远不会成为实例方法。由于您直接将其分配给实例,因此该类没有机会拦截访问并将其包装在实例方法中。

您可以在任何您喜欢的地方分配绑定方法,它仍然绑定到您获得它的实例。请参阅以下示例:

>>> class C(object):
...     def __init__(self, name):
...         self.name = name
...     def func(self, a):
...         print("Called {0} with {1}".format(self.name, a))
>>> one = C("one")
>>> two = C("two")
>>> one.func(1)
Called one with 1
>>> two.func(1)
Called two with 1
>>> one.func2 = two.func
>>> one.func2(1)
Called two with 1

请注意,在我分配one.func2 = two.func后,调用one.func2会调用绑定到two的实例方法,而不是one。这是因为当我访问two.func时,我得到了一个绑定到该实例的方法。我稍后分配它并不重要。它仍然与访问它的实例绑定。

如果您直接访问未绑定的方法并尝试将其分配到其他位置,那么您将获得TypeError,因为该方法从未绑定:

>>> one.func3 = C.func
>>> one.func3(1)
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    one.func3(1)
TypeError: unbound method func() must be called with C instance as first argument (got int instance instead)

另请注意,如果将方法添加到,则在实例上调用时它将起作用,即使在将方法添加到类之前创建了实例也是如此。保持与以前相同的one对象,我可以这样做:

>>> def newFunc(self, a):
...     print("Newfunc of {0} called with {1}".format(self.name, a))
>>> C.newFunc = newFunc
>>> one.newFunc(1)
Newfunc of one called with 1

要简洁地回答您的问题,如果c.func是具有方法cc的类的实例,func将绑定到c本身不具有实例属性func。 (如果c确实具有相同名称的实例属性,那么它将覆盖第一类,因此该类不会进行包装以将方法绑定到实例。)

答案 1 :(得分:1)

当方法c.func(1)绑定到对象c时,

c.func会将实例c神奇地添加为第一个参数。

作为类属性的函数会自动绑定到该类的实例。因此,在c = C()之后,c.funcC.func不同,因为它接受少一个参数,c神奇地成为第一个参数。这就是c.func = c.func没有改变任何东西,一切正常的原因。

如果要使用新的实例方法修补类的实例,则需要将该函数绑定到实例。以下是一些选项:

  • 使用types.MethodType

    import types
    c.func = types.MethodType(newfunc, c)
    
  • 使用functools.partial

    import functools
    c.func = functools.partial(newfunc, c)
    

请注意,第二种方法模仿了行为,但略有不同,因为type(c.func)将是<type 'functools.partial'>而不是<type 'instancemethod'>,所以第一种方法可能更可取。