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

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


当前回答

由于这个问题是针对非Python版本提出的,这里是JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }

其他回答

在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的例子,这是我当前工作中使用的语言。

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

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

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

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

这实际上是对“杰森·普拉特”答案的补充

虽然Jasons的答案是有效的,但它只在想要向类中添加函数时有效。当我试图从.py源代码文件重新加载一个已经存在的方法时,它对我来说并不起作用。

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

例子:

# 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

感谢Arturo!你的回答让我步入正轨!

根据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

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

编辑:

这里有一个更普遍的解决方案:

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