Python: 在运行时更改方法和属性

我希望在 Python 中创建一个可以添加和删除属性和方法的类。我怎么才能做到呢?

别问为什么。

66877 次浏览

此示例显示了向类中添加方法和向实例中添加方法之间的区别。

>>> class Dog():
...     def __init__(self, name):
...             self.name = name
...
>>> skip = Dog('Skip')
>>> spot = Dog('Spot')
>>> def talk(self):
...     print 'Hi, my name is ' + self.name
...
>>> Dog.talk = talk # add method to class
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk()
Hi, my name is Spot
>>> del Dog.talk # remove method from class
>>> skip.talk() # won't work anymore
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'
>>> import types
>>> f = types.MethodType(talk, skip, Dog)
>>> skip.talk = f # add method to specific instance
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk() # won't work, since we only modified skip
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'

使用 types.MethodType的一个可能有趣的替代方法是:

>>> f = types.MethodType(talk, puppy, Dog)
>>> puppy.talk = f # add method to specific instance

就是利用函数是 描述符这一事实:

>>> puppy.talk = talk.__get__(puppy, Dog)

我希望在 Python 中创建一个可以添加和删除属性和方法的类。

import types


class SpecialClass(object):
@classmethod
def removeVariable(cls, name):
return delattr(cls, name)


@classmethod
def addMethod(cls, func):
return setattr(cls, func.__name__, types.MethodType(func, cls))


def hello(self, n):
print n


instance = SpecialClass()
SpecialClass.addMethod(hello)


>>> SpecialClass.hello(5)
5


>>> instance.hello(6)
6


>>> SpecialClass.removeVariable("hello")


>>> instance.hello(7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'SpecialClass' object has no attribute 'hello'


>>> SpecialClass.hello(8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'SpecialClass' has no attribute 'hello'

我希望在 Python 中创建一个可以添加和删除属性和方法的类。我怎么才能做到呢?

您可以向任何类添加和删除属性和方法,这些属性和方法对该类的所有实例都可用:

>>> def method1(self):
pass


>>> def method1(self):
print "method1"


>>> def method2(self):
print "method2"


>>> class C():
pass


>>> c = C()
>>> c.method()


Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
c.method()
AttributeError: C instance has no attribute 'method'


>>> C.method = method1
>>> c.method()
method1
>>> C.method = method2
>>> c.method()
method2
>>> del C.method
>>> c.method()


Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
c.method()
AttributeError: C instance has no attribute 'method'
>>> C.attribute = "foo"
>>> c.attribute
'foo'
>>> c.attribute = "bar"
>>> c.attribute
'bar'

另一种选择是,如果需要替换整个类,可以修改 同学们属性:

>>> class A(object):
...     def foo(self):
...         print 'A'
...
>>> class B(object):
...     def foo(self):
...         print 'Bar'
...
>>> a = A()
>>> a.foo()
A
>>> a.__class__ = B
>>> a.foo()
Bar

简单来说:

f1 = lambda:0                   #method for instances
f2 = lambda _:0                 #method for class
class C: pass                   #class


c1,c2 = C(),C()                 #instances


print dir(c1),dir(c2)


#add to the Instances
c1.func = f1
c1.any = 1.23


print dir(c1),dir(c2)
print c1.func(),c1.any


del c1.func,c1.any


#add to the Class
C.func = f2
C.any = 1.23


print dir(c1),dir(c2)
print c1.func(),c1.any
print c2.func(),c2.any

结果是:

['__doc__', '__module__'] ['__doc__', '__module__']
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__']
0 1.23
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__', 'any', 'func']
0 1.23
0 1.23

你可以直接赋值给类(通过访问原始的类名或者通过 __class__) :

class a : pass
ob=a()
ob.__class__.blah=lambda self,k: (3, self,k)
ob.blah(5)
ob2=a()
ob2.blah(7)

将打印

(3, <__main__.a instance at 0x7f18e3c345f0>, 5)
(3, <__main__.a instance at 0x7f18e3c344d0>, 7)

类本身是否需要修改?或者目标仅仅是替换 object.method ()在运行时的特定点上做什么?

之所以这样问,是因为我回避了在框架中使用 获得属性和 Base 继承对象上的 Runtime Decorator 实际修改类以修改特定于补丁的方法调用的问题。

获得属性中的 Base 对象检索的方法封装在 Runtime _ Decorator 中,该方法解析方法调用的关键字参数,以便应用修饰器/猴子补丁。

这使您能够利用语法 object.method (Monkey _ patch = “ mypatch”)、 object.method (decorator = “ mydecorator”) ,甚至 object.method (decorators = my _ decorator _ list)。

这适用于任何单独的方法调用(我省略了魔法方法) ,不需要实际修改任何类/实例属性,可以利用任意的、甚至是外部的方法进行修补,并且可以透明地在从 Base 继承的子类上工作(当然前提是它们不覆盖 获得属性)。

import trace


def monkey_patched(self, *args, **kwargs):
print self, "Tried to call a method, but it was monkey patched instead"
return "and now for something completely different"


class Base(object):


def __init__(self):
super(Base, self).__init__()


def testmethod(self):
print "%s test method" % self


def __getattribute__(self, attribute):
value = super(Base, self).__getattribute__(attribute)
if "__" not in attribute and callable(value):
value = Runtime_Decorator(value)
return value


class Runtime_Decorator(object):


def __init__(self, function):
self.function = function


def __call__(self, *args, **kwargs):


if kwargs.has_key("monkey_patch"):
module_name, patch_name = self._resolve_string(kwargs.pop("monkey_patch"))
module = self._get_module(module_name)
monkey_patch = getattr(module, patch_name)
return monkey_patch(self.function.im_self, *args, **kwargs)


if kwargs.has_key('decorator'):
decorator_type = str(kwargs['decorator'])


module_name, decorator_name = self._resolve_string(decorator_type)
decorator = self._get_decorator(decorator_name, module_name)
wrapped_function = decorator(self.function)
del kwargs['decorator']
return wrapped_function(*args, **kwargs)


elif kwargs.has_key('decorators'):
decorators = []


for item in kwargs['decorators']:
module_name, decorator_name = self._resolve_string(item)
decorator = self._get_decorator(decorator_name, module_name)
decorators.append(decorator)


wrapped_function = self.function
for item in reversed(decorators):
wrapped_function = item(wrapped_function)
del kwargs['decorators']
return wrapped_function(*args, **kwargs)


else:
return self.function(*args, **kwargs)


def _resolve_string(self, string):
try: # attempt to split the string into a module and attribute
module_name, decorator_name = string.split(".")
except ValueError: # there was no ".", it's just a single attribute
module_name = "__main__"
decorator_name = string
finally:
return module_name, decorator_name


def _get_module(self, module_name):
try: # attempt to load the module if it exists already
module = modules[module_name]
except KeyError: # import it if it doesn't
module = __import__(module_name)
finally:
return module


def _get_decorator(self, decorator_name, module_name):
module = self._get_module(module_name)
try: # attempt to procure the decorator class
decorator_wrap = getattr(module, decorator_name)
except AttributeError: # decorator not found in module
print("failed to locate decorators %s for function %s." %\
(kwargs["decorator"], self.function))
else:
return decorator_wrap # instantiate the class with self.function


class Tracer(object):


def __init__(self, function):
self.function = function


def __call__(self, *args, **kwargs):
tracer = trace.Trace(trace=1)
tracer.runfunc(self.function, *args, **kwargs)


b = Base()
b.testmethod(monkey_patch="monkey_patched")
b.testmethod(decorator="Tracer")
#b.testmethod(monkey_patch="external_module.my_patch")

这种方法的缺点是 获得属性钩住了 所有对属性的访问,因此即使对于不是方法 + 的属性,也会检查和包装方法,而不会为所讨论的特定调用使用这个特性。而且使用 获得属性本身就有些复杂。

根据我的经验/就我的目的而言,这种开销的实际影响是可以忽略不计的,而且我的机器运行的是双核 Celeron。在前面的实现中,我在对象 Init上使用了内省方法,然后将 Runtime _ Decorator 绑定到方法上。这样做的方式消除了利用 获得属性的需要,并减少了前面提到的开销... 然而,它也打破了 pickle (也许不是莳萝) ,并且没有这种方法那么动态。

我实际遇到的使用这种技术的“野外”用例只有计时和跟踪装饰器。然而,它打开的可能性是非常广泛的。

如果你有一个先前存在的类,不能从不同的基类继承(或者利用它自己的类定义或者它的基类’中的技术) ,那么很不幸,整个事情根本不适用于你的问题。

我不认为在运行时设置/删除一个类的不可调用属性有必要如此具有挑战性?除非你想让那些从修改后的类继承的类自动反映它们自身的变化... ... 不过听起来就像是一个完全的“另一个可以改变的蠕虫”。