在 Python 中向现有对象实例添加方法

Adding a method to an existing object instance in Python

提问人:akdom 提问时间:8/4/2008 最后编辑:Mateen Ulhaqakdom 更新时间:6/18/2023 访问量:375551

问:

如何在 Python 中将方法添加到现有对象(即不在类定义中)?

我知道这样做通常不被认为是好的做法,除非在某些情况下。

python oop 方法 monkeypatching

评论


答:

32赞 John Downey 8/4/2008 #1

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

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

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

评论

11赞 glglgl 6/3/2014
但它会影响类的所有实例,而不仅仅是一个实例。
1赞 John Henckel 4/29/2022
谢谢,我来这里是为了寻找一种影响所有实例的方法。
1160赞 Jason Pratt 8/6/2008 #2

在 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>

为了绑定它,我们可以在 types 模块中使用 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'

有关详细信息,请参阅有关描述符元类编程的信息

评论

109赞 Martijn Pieters 9/16/2016
而不是手动创建一个 ,手动调用描述符协议并让函数生成您的实例: 生成绑定到 的绑定方法。MethodTypebarFighters.__get__(a)barFightersa
5赞 EndermanAPM 5/10/2017
@MartijnPieters使用与创建可能更具可读性的旁白有什么好处。descriptor protocolMethodType
23赞 Martijn Pieters 5/10/2017
@EndermanAPM:几个:它更有可能继续工作,就像访问实例上的属性一样。它也适用于 和 和其他描述符。它避免了另一个导入使命名空间混乱。classmethodstaticmethod
66赞 eqzx 9/15/2017
建议的描述符方法的完整代码是a.barFighters = barFighters.__get__(a)
4赞 oeter 2/1/2021
请注意,python3 中没有未绑定的方法,因为函数和未绑定方法之间的区别非常小,Python 3 摆脱了这种区别。
12赞 HS. 8/7/2008 #3

你要找的是我相信。 使用它来设置对象的属性。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>

评论

8赞 Ethan Furman 9/25/2011
这是修补类,而不是实例。Aa
10赞 Tobias Kienzler 8/9/2013
有没有理由使用而不是简单地使用?setattr(A,'printme',printme)A.printme = printme
4赞 rr- 4/2/2016
如果在运行时构造方法名称,则有意义。
3赞 Acuminate 8/22/2008 #4

杰森·普拉特(Jason Pratt)发布的是正确的。

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

正如你所看到的,Python 认为 b() 和 a() 没有任何不同。在 Python 中,所有方法都只是恰好是函数的变量。

评论

7赞 Ethan Furman 9/25/2011
您正在修补类,而不是它的实例。Test
0赞 TomSawyer 8/29/2019
您正在向类添加方法,而不是向对象实例添加方法。
105赞 6 revs, 3 users 97%Evgeny #5

自 python 2.6 以来,新模块已被弃用,并在 3.0 中删除,使用类型

查看 http://docs.python.org/library/new.html

在下面的示例中,我特意从函数中删除了返回值。 我认为给出返回值可能会让人相信 patch 返回一个新对象,这是不正确的 - 它会修改传入的对象。也许这可以促进更有纪律地使用猴子补丁。patch_me()

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>

评论

3赞 WesR 1/31/2020
如果添加的方法需要引用 self,这将如何工作?我的第一次尝试导致语法错误,但是在方法定义中添加 self 似乎不起作用。导入类型类 A(对象):#but 似乎也适用于旧式对象 ax = 'ax' 传递 def patch_me(target): def method(target,x): print (self.ax) print (“x=”,x) print (“called from”, target) target.method = types.MethodType(method,target) #add 更多,如果需要 a = A() print(a.ax)
42赞 Tomasz Zieliński 1/22/2012 #6

我认为上面的答案没有抓住关键点。

让我们有一个带有方法的类:

class A(object):
    def m(self):
        pass

现在,让我们在 ipython 中使用它:

In [2]: A.m
Out[2]: <unbound method A.m>

好的,所以 m() 以某种方式成为 A 的未绑定方法。但真的是这样吗?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

事实证明,m() 只是一个函数,对它的引用被添加到 A 类字典中 - 没有魔法。那为什么 A.m 给了我们一个未绑定的方法呢?这是因为点没有翻译成简单的字典查找。它实际上是 A.__class__.__getattribute__(A, 'm') 的调用:

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

现在,我不确定为什么最后一行被打印了两次,但仍然很清楚那里发生了什么。

现在,默认__getattribute__的作用是检查属性是否是所谓的描述符,即它是否实现了特殊的__get__方法。如果它实现该方法,则返回的是调用该 __get__ 方法的结果。回到我们 A 类的第一个版本,这就是我们所拥有的:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

由于 Python 函数实现了描述符协议,因此如果代表对象调用它们,它们会在 __get__ 方法中将自己绑定到该对象。

好的,那么如何向现有对象添加方法呢?假设你不介意修补类,它就像:

B.m = m

然后 B.m “成为”一个未绑定的方法,这要归功于描述符的魔力。

如果你只想向单个对象添加一个方法,那么你必须自己使用类型来模拟机器。方法类型:

b.m = types.MethodType(m, b)

顺便一提:

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>
7赞 Nisan.H 1/28/2012 #7

整合 Jason Pratt 和社区 wiki 的答案,看看不同绑定方法的结果:

特别要注意将绑定函数添加为类方法的工作原理,但引用范围不正确。

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

就我个人而言,我更喜欢外部 ADDMETHOD 函数路由,因为它也允许我在迭代器中动态分配新的方法名称。

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>

评论

0赞 Antony Hatchkins 4/6/2015
addmethod以下列方式重写解决了问题def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )
7赞 Tamzin Blake 3/9/2012 #8

由于这个问题要求非 Python 版本,因此这里是 JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
11赞 ndpu 4/26/2013 #9

至少有两种方法可以将方法附加到实例,而无需:types.MethodType

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

有用的链接:
数据模型 - 调用描述符 描述符操作指南 - 调用描述符

4赞 ChristopherC 7/15/2014 #10

如果它能帮上忙,我最近发布了一个名为 Gorilla 的 Python 库,让猴子打补丁的过程更加方便。

使用函数修补名为的模块如下所示:needle()guineapig

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

但它也处理了更有趣的用例,如文档中的常见问题解答所示。

该代码可在 GitHub 上找到。

20赞 Evgeny Prokurat 7/21/2014 #11

您可以使用 lambda 将方法绑定到实例:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

输出:

This is instance string

评论

1赞 David R 6/7/2022
我会喜欢这个答案(如此简单和优雅!),但是当我尝试使用它时,我得到了一个无法选择的对象:“Can't pickle <function <lambda> at 0x0000014B73FE2B80>: attribute lookup <lambda> on main failed”
0赞 Yaroslav Nikitenko 6/20/2022
@DavidR你是对的,lambda 不能腌制。我一直在思考这个解决方案的缺点,你在这里指出了这一点,非常感谢!
180赞 Russia Must Remove Putin 1/21/2015 #12

前言 - 关于兼容性的说明:其他答案可能只在 Python 2 中有效 - 这个答案应该在 Python 2 和 3 中完全有效。如果只编写 Python 3,则可以省略显式继承自 ,但除此之外,代码应保持不变。object

向现有对象实例添加方法

我读到可以在 Python 中向现有对象(例如不在类定义中)添加方法。

我知道这样做并不总是一个好的决定。但是,如何做到这一点呢?

是的,这是可能的 - 但不推荐

我不建议这样做。这是个坏主意。别这样。

原因如下:

  • 您将向执行此操作的每个实例添加一个绑定对象。如果你经常这样做,你可能会浪费很多内存。绑定方法通常仅在其调用的短时间内创建,然后在自动垃圾回收时它们将不复存在。如果手动执行此操作,则将有一个引用绑定方法的名称绑定 - 这将防止其在使用时进行垃圾回收。
  • 给定类型的对象实例通常对该类型的所有对象都有其方法。如果将方法添加到其他位置,则某些实例将具有这些方法,而其他实例则不会。程序员不会预料到这一点,你就有可能违反最小意外的规则
  • 由于还有其他很好的理由不这样做,因此如果您这样做,您还会给自己带来不好的声誉。

因此,我建议你不要这样做,除非你有一个很好的理由。最好在类定义中定义正确的方法,或者最好直接对类进行猴子修补,如下所示:

Foo.sample_method = sample_method

但是,由于它很有启发性,我将向您展示一些执行此操作的方法。

如何做到

下面是一些设置代码。我们需要一个类定义。它可以导入,但真的无关紧要。

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

创建实例:

foo = Foo()

创建一个方法以添加到其中:

def sample_method(self, bar, baz):
    print(bar + baz)

method nought (0) - 使用描述符方法,__get__

函数上的虚线查找使用实例调用函数的方法,将对象绑定到方法,从而创建“绑定方法”。__get__

foo.sample_method = sample_method.__get__(foo)

现在:

>>> foo.sample_method(1,2)
3

方法一 - 类型。方法类型

首先,导入类型,我们将从中获取方法构造函数:

import types

现在我们将方法添加到实例中。为此,我们需要模块中的 MethodType 构造函数(我们在上面导入)。types

类型的参数签名。MethodType(在 Python 3 中)是:(function, instance)

foo.sample_method = types.MethodType(sample_method, foo)

和用法:

>>> foo.sample_method(1,2)
3

括号内,在 Python 2 中,签名是:(function, instance, class)

foo.sample_method = types.MethodType(sample_method, foo, Foo)

方法二:词法绑定

首先,我们创建一个包装函数,将方法绑定到实例:

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

用法:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

方法三:functools.partial

分部函数将第一个参数应用于函数(以及可选的关键字参数),稍后可以使用其余参数(和覆盖关键字参数)进行调用。因此:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

当您认为绑定方法是实例的部分函数时,这是有道理的。

Unbound 函数作为对象属性 - 为什么这不起作用:

如果我们尝试以与将sample_method添加到类相同的方式添加,则它与实例没有绑定,并且不会将隐式自我作为第一个参数。

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

我们可以通过显式传递实例(或任何东西,因为此方法实际上并不使用参数变量)来使未绑定函数工作,但它与其他实例的预期签名不一致(如果我们要对这个实例进行猴子修补):self

>>> foo.sample_method(foo, 1, 2)
3

结论

你现在知道了几种方法可以做到这一点,但严肃地说——不要这样做。

评论

1赞 Atcold 1/19/2018
免责声明是我想知道的。方法定义只是嵌套在类定义中的函数。
1赞 Russia Must Remove Putin 1/19/2018
@Atcold我在引言中详细阐述了避免这样做的原因。
2赞 Russia Must Remove Putin 1/31/2018
@AidasBendoraitis我不会说它“需要”它,它是应用描述符协议时提供的可选参数 - 但 python 函数不使用参数:github.com/python/cpython/blob/master/Objects/funcobject.c#L581
1赞 Carl Boneri 1/28/2021
很好的解释和方法。
2赞 z33k 11/24/2021
@AaronHall:我认为这实际上是一种有用的嘲笑技巧。比如说,你想用一个定义的实现来对你的模拟对象进行猴子修补。很容易认为这会起作用......mock.sample_method = SampleClass.sample_method
7赞 Max 8/18/2015 #13

这实际上是“杰森·普拉特”答案的附加内容

尽管 Jasons 的答案有效,但它仅在想要向类添加函数时才有效。 当我尝试从 .py 源代码文件重新加载已经存在的方法时,它对我不起作用。

我花了很长时间才找到解决方法,但诀窍似乎很简单...... 1.st 从源代码文件导入代码 2.nd 强制重新加载 3.RD使用类型。FunctionType(...) 将导入和绑定的方法转换为函数 还可以传递当前的全局变量,因为 reloaded 方法将位于不同的命名空间中 4.th 现在您可以按照“Jason Pratt”的建议继续 使用类型。方法类型(...)

例:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"
        
    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code
5赞 lain 12/22/2015 #14

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

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 ()

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

总之,你会得到一个函数,模拟它绑定到类实例。保持原有功能不变。

5赞 Yu Feng 4/30/2017 #15

我觉得奇怪的是,没有人提到上面列出的所有方法都在添加的方法和实例之间创建了一个循环引用,导致对象在垃圾回收之前是持久的。有一个古老的技巧,通过扩展对象的类来添加描述符:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass

评论

0赞 Yaroslav Nikitenko 6/20/2022
您能否证明一下关于“周期参考”的这个想法?我认为这是错误的(在 stackoverflow.com/a/55414240/952234 的评论中对此进行了扩展)。
4赞 Arturo Morales Rangel 7/27/2017 #16
from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

有了这个,你就可以使用自指针了

-1赞 danbst 11/22/2020 #17

除了其他人说的,我发现 and 方法不能在对象级别上被猴子修补,因为 和 使用类方法,而不是本地边界的对象方法:__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'
0赞 Gerard G 1/11/2022 #18

如何从类的实例中恢复类

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')
0赞 Hans 8/25/2022 #19

感谢阿图罗! 你的回答让我走上了正确的轨道!

根据 Arturo 的代码,我写了一个小类:

from types import MethodType
import re
from string import ascii_letters


class DynamicAttr:
    def __init__(self):
        self.dict_all_files = {}

    def _copy_files(self, *args, **kwargs):
        print(f'copy {args[0]["filename"]} {args[0]["copy_command"]}')

    def _delete_files(self, *args, **kwargs):
        print(f'delete {args[0]["filename"]} {args[0]["delete_command"]}')

    def _create_properties(self):
        for key, item in self.dict_all_files.items():
            setattr(
                self,
                key,
                self.dict_all_files[key],
            )
            setattr(
                self,
                key + "_delete",
                MethodType(
                    self._delete_files,
                    {
                        "filename": key,
                        "delete_command": f'del {item}',
                    },
                ),
            )
            setattr(
                self,
                key + "_copy",
                MethodType(
                    self._copy_files,
                    {
                        "filename": key,
                        "copy_command": f'copy {item}',
                    },
                ),
            )
    def add_files_to_class(self, filelist: list):
        for _ in filelist:
            attr_key = re.sub(rf'[^{ascii_letters}]+', '_', _).strip('_')
            self.dict_all_files[attr_key] = _
        self._create_properties()
dy = DynamicAttr()
dy.add_files_to_class([r"C:\Windows\notepad.exe", r"C:\Windows\regedit.exe"])

dy.add_files_to_class([r"C:\Windows\HelpPane.exe", r"C:\Windows\win.ini"])
#output
print(dy.C_Windows_HelpPane_exe)
dy.C_Windows_notepad_exe_delete()
dy.C_Windows_HelpPane_exe_copy()
C:\Windows\HelpPane.exe
delete C_Windows_notepad_exe del C:\Windows\notepad.exe
copy C_Windows_HelpPane_exe copy C:\Windows\HelpPane.exe

result

此类允许您随时添加新的属性和方法。

编辑:

下面是一个更通用的解决方案:

import inspect
import re
from copy import deepcopy
from string import ascii_letters


def copy_func(f):
    if callable(f):
        if inspect.ismethod(f) or inspect.isfunction(f):
            g = lambda *args, **kwargs: f(*args, **kwargs)
            t = list(filter(lambda prop: not ("__" in prop), dir(f)))
            i = 0
            while i < len(t):
                setattr(g, t[i], getattr(f, t[i]))
                i += 1
            return g
    dcoi = deepcopy([f])
    return dcoi[0]


class FlexiblePartial:
    def __init__(self, func, this_args_first, *args, **kwargs):

        try:
            self.f = copy_func(func)  # create a copy of the function
        except Exception:
            self.f = func
        self.this_args_first = this_args_first  # where should the other (optional) arguments be that are passed when the function is called
        try:
            self.modulename = args[0].__class__.__name__  # to make repr look good
        except Exception:
            self.modulename = "self"

        try:
            self.functionname = func.__name__  # to make repr look good
        except Exception:
            try:
                self.functionname = func.__qualname__  # to make repr look good
            except Exception:
                self.functionname = "func"

        self.args = args
        self.kwargs = kwargs

        self.name_to_print = self._create_name()  # to make repr look good

    def _create_name(self):
        stra = self.modulename + "." + self.functionname + "(self, "
        for _ in self.args[1:]:
            stra = stra + repr(_) + ", "
        for key, item in self.kwargs.items():
            stra = stra + str(key) + "=" + repr(item) + ", "
        stra = stra.rstrip().rstrip(",")
        stra += ")"
        if len(stra) > 100:
            stra = stra[:95] + "...)"
        return stra

    def __call__(self, *args, **kwargs):
        newdic = {}
        newdic.update(self.kwargs)
        newdic.update(kwargs)
        if self.this_args_first:
            return self.f(*self.args[1:], *args, **newdic)

        else:

            return self.f(*args, *self.args[1:], **newdic)

    def __str__(self):
        return self.name_to_print

    def __repr__(self):
        return self.__str__()


class AddMethodsAndProperties:
    def add_methods(self, dict_to_add):
        for key_, item in dict_to_add.items():
            key = re.sub(rf"[^{ascii_letters}]+", "_", str(key_)).rstrip("_")
            if isinstance(item, dict):
                if "function" in item:  # for adding methods
                    if not isinstance(
                        item["function"], str
                    ):  # for external functions that are not part of the class
                        setattr(
                            self,
                            key,
                            FlexiblePartial(
                                item["function"],
                                item["this_args_first"],
                                self,
                                *item["args"],
                                **item["kwargs"],
                            ),
                        )

                    else:
                        setattr(
                            self,
                            key,
                            FlexiblePartial(
                                getattr(
                                    self, item["function"]
                                ),  # for internal functions - part of the class
                                item["this_args_first"],
                                self,
                                *item["args"],
                                **item["kwargs"],
                            ),
                        )
            else:  # for adding props
                setattr(self, key, item)

让我们来测试一下:

class NewClass(AddMethodsAndProperties): #inherit from AddMethodsAndProperties to add the method add_methods
    def __init__(self):
        self.bubu = 5

    def _delete_files(self, file): #some random methods 
        print(f"File will be deleted: {file}")

    def delete_files(self, file):
        self._delete_files(file)

    def _copy_files(self, file, dst):
        print(f"File will be copied: {file} Dest: {dst}")

    def copy_files(self, file, dst):
        self._copy_files(file, dst)

    def _create_files(self, file, folder):
        print(f"File will be created: {file} {folder}")

    def create_files(self, file, folder):
        self._create_files(file, folder)

    def method_with_more_kwargs(self, file, folder, one_more):
        print(file, folder, one_more)
        return self


nc = NewClass()
dict_all_files = {
    r"C:\Windows\notepad.exe_delete": {
        "function": "delete_files",
        "args": (),
        "kwargs": {"file": r"C:\Windows\notepad.exe"},
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_argsfirst": {
        "function": "delete_files",
        "args": (),
        "kwargs": {"file": r"C:\Windows\notepad.exe"},
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_copy": {
        "function": "copy_files",
        "args": (),
        "kwargs": {
            "file": r"C:\Windows\notepad.exe",
            "dst": r"C:\Windows\notepad555.exe",
        },
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_create": {
        "function": "create_files",
        "args": (),
        "kwargs": {"file": r"C:\Windows\notepad.exe", "folder": "c:\\windows95"},
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_upper": {
        "function": str.upper,
        "args": (r"C:\Windows\notepad.exe",),
        "kwargs": {},
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_method_with_more_kwargs": {
        "function": "method_with_more_kwargs",
        "args": (),
        "kwargs": {"file": r"C:\Windows\notepad.exe", "folder": "c:\\windows95"},
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_method_with_more_kwargs_as_args_first": {
        "function": "method_with_more_kwargs",
        "args": (r"C:\Windows\notepad.exe", "c:\\windows95"),
        "kwargs": {},
        "this_args_first": True,
    },
    r"C:\Windows\notepad.exe_method_with_more_kwargs_as_args_last": {
        "function": "method_with_more_kwargs",
        "args": (r"C:\Windows\notepad.exe", "c:\\windows95"),
        "kwargs": {},
        "this_args_first": False,
    },
    "this_is_a_list": [55, 3, 3, 1, 4, 43],
}

nc.add_methods(dict_all_files)


print(nc.C_Windows_notepad_exe_delete)
print(nc.C_Windows_notepad_exe_delete(), end="\n\n")
print(nc.C_Windows_notepad_exe_argsfirst)
print(nc.C_Windows_notepad_exe_argsfirst(), end="\n\n")
print(nc.C_Windows_notepad_exe_copy)
print(nc.C_Windows_notepad_exe_copy(), end="\n\n")
print(nc.C_Windows_notepad_exe_create)
print(nc.C_Windows_notepad_exe_create(), end="\n\n")
print(nc.C_Windows_notepad_exe_upper)
print(nc.C_Windows_notepad_exe_upper(), end="\n\n")
print(nc.C_Windows_notepad_exe_method_with_more_kwargs)
print(
    nc.C_Windows_notepad_exe_method_with_more_kwargs(
        one_more="f:\\blaaaaaaaaaaaaaaaaaaaaaaaa"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs(
        one_more="f:\\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs(
        one_more="f:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ),
    end="\n\n",
)
print(nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_first)
print(
    nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_first(
        "f:\\blaaaaaaaaaaaaaaaaaaaaaaaa"
    ),
    end="\n\n",
)
print(
    nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_first(
        "f:\\blaaaaaaaaaaaaaaaaaaaaaaaa"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_first(
        "f:\\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_first(
        "f:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ),
    end="\n\n",
)
print(nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_last)
print(
    nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\blaaaaaaaaaaaaaaaaaaaaaaaa"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ),
    end="\n\n",
)
print(
    nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\blaaaaaaaaaaaaaaaaaaaaaaaa"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ),
    end="\n\n",
)
print(nc.this_is_a_list)
checkit = (
    nc.C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\blaaaaaaaaaaaaaaaaaaaaaaaa"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ"
    )
    .C_Windows_notepad_exe_method_with_more_kwargs_as_args_last(
        "f:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    )
)
print(f'nc is checkit? -> {nc is checkit}')


#output:


NewClass.delete_files(self, file='C:\\Windows\\notepad.exe')
File will be deleted: C:\Windows\notepad.exe
None


NewClass.delete_files(self, file='C:\\Windows\\notepad.exe')
File will be deleted: C:\Windows\notepad.exe
None


NewClass.copy_files(self, file='C:\\Windows\\notepad.exe', dst='C:\\Windows\\notepad555.exe')
File will be copied: C:\Windows\notepad.exe Dest: C:\Windows\notepad555.exe
None


NewClass.create_files(self, file='C:\\Windows\\notepad.exe', folder='c:\\windows95')
File will be created: C:\Windows\notepad.exe c:\windows95
None


NewClass.upper(self, 'C:\\Windows\\notepad.exe')
C:\WINDOWS\NOTEPAD.EXE


NewClass.method_with_more_kwargs(self, file='C:\\Windows\\notepad.exe', folder='c:\\windows95')
C:\Windows\notepad.exe c:\windows95 f:\blaaaaaaaaaaaaaaaaaaaaaaaa
C:\Windows\notepad.exe c:\windows95 f:\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ
C:\Windows\notepad.exe c:\windows95 f:\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<__main__.NewClass object at 0x0000000005F199A0>


NewClass.method_with_more_kwargs(self, 'C:\\Windows\\notepad.exe', 'c:\\windows95')
C:\Windows\notepad.exe c:\windows95 f:\blaaaaaaaaaaaaaaaaaaaaaaaa
<__main__.NewClass object at 0x0000000005F199A0>


C:\Windows\notepad.exe c:\windows95 f:\blaaaaaaaaaaaaaaaaaaaaaaaa
C:\Windows\notepad.exe c:\windows95 f:\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ
C:\Windows\notepad.exe c:\windows95 f:\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<__main__.NewClass object at 0x0000000005F199A0>


NewClass.method_with_more_kwargs(self, 'C:\\Windows\\notepad.exe', 'c:\\windows95')
f:\blaaaaaaaaaaaaaaaaaaaaaaaa C:\Windows\notepad.exe c:\windows95
f:\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ C:\Windows\notepad.exe c:\windows95
f:\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX C:\Windows\notepad.exe c:\windows95
<__main__.NewClass object at 0x0000000005F199A0>


f:\blaaaaaaaaaaaaaaaaaaaaaaaa C:\Windows\notepad.exe c:\windows95
f:\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ C:\Windows\notepad.exe c:\windows95
f:\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX C:\Windows\notepad.exe c:\windows95
<__main__.NewClass object at 0x0000000005F199A0>


[55, 3, 3, 1, 4, 43]


f:\blaaaaaaaaaaaaaaaaaaaaaaaa C:\Windows\notepad.exe c:\windows95
f:\ASJVASDFASÇDFJASÇDJFÇASWFJASÇ C:\Windows\notepad.exe c:\windows95
f:\XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX C:\Windows\notepad.exe c:\windows95


nc is checkit? -> True

评论

1赞 robertspierre 9/29/2023
如果要共享,则应提供 MWE。这是无法理解的。
0赞 Hans 9/29/2023
查看下面的文字“让我们测试一下:”
1赞 robertspierre 9/29/2023
是的。那不是 MWE。
0赞 Hans 9/29/2023
很抱歉,我没有遵循您对 MWE 的定义。下次我会做得更好。
1赞 robertspierre 10/22/2023
这是 StackOverflow 的定义,不是我的