我已经了解到,可以在Python中向现有对象(即,不在类定义中)添加方法。

我明白这样做并不总是好的。但你怎么能做到这一点呢?


当前回答

在Python中,猴痘通常通过用自己的签名覆盖类或函数的签名来工作。以下是Zope Wiki的示例:

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak

此代码将覆盖/创建类中名为speak的方法。在Jeff Atwood最近发表的关于猴子修补的文章中,他展示了一个C#3.0的例子,这是我当前工作中使用的语言。

其他回答

在Python中,函数和绑定方法之间存在差异。

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

绑定方法已“绑定”到一个实例(如何描述),每当调用该方法时,该实例将作为第一个参数传递。

但是,作为类(与实例相反)属性的可调用项仍然是未绑定的,因此您可以随时修改类定义:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

以前定义的实例也会更新(只要它们没有覆盖属性本身):

>>> a.fooFighters()
fooFighters

当您想将方法附加到单个实例时,问题就出现了:

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

函数直接附加到实例时不会自动绑定:

>>> a.barFighters
<function barFighters at 0x00A98EF0>

要绑定它,我们可以在类型模块中使用MethodType函数:

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

这次该类的其他实例没有受到影响:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'

通过阅读描述符和元类编程可以找到更多信息。

我相信你要找的是setattr。使用此设置对象的属性。

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>

除了其他人所说的,我发现__repr_和__str__方法不能在对象级别上进行猴痘,因为repr()和str()使用类方法,而不是本地绑定的对象方法:

# Instance monkeypatch
[ins] In [55]: x.__str__ = show.__get__(x)                                                                 

[ins] In [56]: x                                                                                           
Out[56]: <__main__.X at 0x7fc207180c10>

[ins] In [57]: str(x)                                                                                      
Out[57]: '<__main__.X object at 0x7fc207180c10>'

[ins] In [58]: x.__str__()                                                                                 
Nice object!

# Class monkeypatch
[ins] In [62]: X.__str__ = lambda _: "From class"                                                          

[ins] In [63]: str(x)                                                                                      
Out[63]: 'From class'

这个问题早在几年前就提出了,但嘿,有一种简单的方法可以使用decorator模拟函数与类实例的绑定:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

在那里,当您将函数和实例传递给绑定器装饰器时,它将创建一个新函数,其代码对象与第一个相同。然后,类的给定实例存储在新创建的函数的属性中。装饰器返回一个(第三个)函数,自动调用复制的函数,将实例作为第一个参数。最后,您将得到一个函数,模拟它与类实例的绑定。保持原始函数不变。

如何从类的实例恢复类

class UnderWater:
    def __init__(self):
        self.net = 'underwater'

marine = UnderWater() # Instantiate the class

# Recover the class from the instance and add attributes to it.
class SubMarine(marine.__class__):  
    def __init__(self):
        super().__init__()
            self.sound = 'Sonar'
    
print(SubMarine, SubMarine.__name__, SubMarine().net, SubMarine().sound)

# Output
# (__main__.SubMarine,'SubMarine', 'underwater', 'Sonar')