4
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只是一个实例属性。

4

2 回答 2

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必须绑定cifc是具有方法的类的实例func,并且c本身没有实例属性func。(如果c确实有同名的实例属性,则会覆盖类一,因此该类不会进行包装以将方法绑定到实例。)

于 2012-07-31T21:19:06.567 回答
1

c.func(1)当方法绑定到对象时,将神奇地添加实例c作为第一个参数。c.funcc

作为类属性的函数会自动绑定到该类的实例。因此,紧随其后c = C(),c.func的不同之处C.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)will<type 'functools.partial'>代替<type 'instancemethod'>,所以第一种方法可能更可取。

于 2012-07-31T21:24:41.760 回答