我已经了解到,可以在Python中向现有对象(即,不在类定义中)添加方法。
我明白这样做并不总是好的。但你怎么能做到这一点呢?
我已经了解到,可以在Python中向现有对象(即,不在类定义中)添加方法。
我明白这样做并不总是好的。但你怎么能做到这一点呢?
当前回答
我相信你要找的是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>
其他回答
由于这个问题是针对非Python版本提出的,这里是JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
我相信你要找的是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>
感谢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
前言-关于兼容性的说明:其他答案可能只在Python2-这个答案应该在Python2和3中运行得很好。如果只编写Python3,您可能会忽略显式继承自对象,否则代码应该保持不变。
向现有对象实例添加方法我已经了解到,可以在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)
方法零(0)-使用描述符方法__get__
函数上的点查找使用实例调用函数的__get__方法,将对象绑定到方法,从而创建“绑定方法”
foo.sample_method = sample_method.__get__(foo)
现在:
>>> foo.sample_method(1,2)
3
方法一-types.MethodType
首先,导入类型,我们将从中获取方法构造函数:
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中,签名是(函数、实例、类):
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
当您认为绑定方法是实例的部分函数时,这是有意义的。
作为对象属性的未绑定函数-为什么不起作用:
如果我们尝试以与将sample_method添加到类中相同的方式添加sample_methods,它将与实例绑定,并且不会将隐式self作为第一个参数。
>>> 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)
我们可以通过显式传递实例(或任何东西,因为此方法实际上不使用自参数变量)来使未绑定函数工作,但它与其他实例的预期签名不一致(如果我们正在对该实例进行猴子修补):
>>> foo.sample_method(foo, 1, 2)
3
结论
你现在知道有几种方法可以做到这一点,但认真地说,不要这样做。
可以使用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