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


Answers:


47

我希望在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'

9
请注意,这会向SpecialClass添加一个类方法。它没有添加将对SpecialClass的所有将来实例可用的方法。(我确实想知道是否有办法做到这一点。)_
M. Elkstein,2010年

真的很有趣。
对话

123

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

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

13
请注意,您只能对执行此操作,而不能对实例执行此操作。如果您执行uppy.talk = talk,talk不会成为“绑定方法”,也就是说,它不会获得隐式的“ self”参数。
Paul Fisher

10
补充一下Paul的评论:如果您想猴子修补实例方法:“导入类型; f = types.MethodType(对话,小狗,狗); puppy.talk = f”
Jarret Hardie

5
向Paolo +1,以演示分配和删除类方法属性的动态效果。
Jarret Hardie

3
感谢大家的好评,我更新了答案以显示差异。
Paolo Bergantino,2009年

8
很好的编辑示例...它几乎应该放在类型模块的Python API文档中,这简直是远远不够的。
Jarret Hardie

28

types.MethodType在以下位置使用的一种可能有趣的替代方法:

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

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

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

2
我刚刚学到了一些东西:)但我认为它看起来不太可读。
Nicolas Dumazet,2009年

+1,如您所说,是不错的替代语法。我很好奇:这种方法或使用“类型”有什么特别的好处吗?最终,它们产生相同的结果和内部绑定AFAICAT。types.MethodType是否有效地产生了一个描述符,或者还有更多的工作吗?
Jarret Hardie

@NicDumZ,是的,__ thingies从未真正看起来很好。@Jarret,在某个时候,Python 3的设计松散地谈论了废止“类型”模块的问题,但是它停留了下来,从37个条目减少到了12个(“新”模块确实行了,是的!-)。从语义上讲,它们实际上是相同的:MethodType返回与get结果相同的对象-< type'instancemethod '>的实例。
Alex Martelli,2009年

5

我希望在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'

4

您可以直接分配给该类(通过访问原始类名或通过__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)

0

另一个替代方法,如果需要替换批发类,则可以修改class属性:

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

有趣,但是重点是在运行时修改方法。对于运行时切换的IoC容器,这似乎是一个主意:)
Migol 2012年

是的,切换类允许您批量修改方法,尤其是当您添加此功能时,python允许多个继承并且python类是可变的,因此它可能会导致一些非常强大的动态技术或非常难以维护的代码。
Lie Ryan

0

只是:

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

0

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

我问是因为我回避了使用getattribute在我的框架中实际修改类以猴子补丁特定方法调用的问题和Base继承对象上的Runtime Decorator。

getattribute中的Base对象检索的方法包装在Runtime_Decorator中,该Runtime_Decorator解析该方法调用关键字参数,以应用装饰器/猴子补丁。

这使您可以利用语法object.method(monkey_patch =“ mypatch”),object.method(decorator =“ mydecorator”)甚至object.method(decorators = my_decorator_list)。

该方法适用于任何单独的方法调用(我不使用魔术方法),无需实际修改任何类/实例属性即可使用,可以利用任意甚至外部方法进行修补,并且可以在继承自Base的sublcasses上透明工作(前提是它们不会当然不会覆盖getattribute)。

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

这种方法的缺点是getattribute钩住对属性的所有访问权限,因此即使对于不是方法+的属性也不会针对特定的调用使用该功能,方法的检查和潜在包装也会发生。完全使用getattribute本质上有些复杂。

在我的经验中/出于我的目的,这些开销的实际影响可以忽略不计,并且我的机器运行双核赛扬。先前的实现我在对象初始化时使用了自省方法,然后将Runtime_Decorator绑定到了方法上。这样做可以消除利用getattribute的需要并减少了前面提到的开销……但是,它也使泡菜(也许不是莳萝)破裂,并且比这种方法动态性更差。

我实际上通过这种技术“在野外”遇到的唯一用例是定时和跟踪装饰器。但是,它打开的可能性非常广泛。

如果您有一个预先存在的类,不能使它继承自其他基类(或使用它自己的类定义或在基类中使用的技术),那么不幸的是,整个问题根本就不适用于您的问题。

我不认为在运行时在类上设置/删除不可调用的属性不一定有挑战性吗?除非您希望从修改后的类继承的类也能自动反映自身的变化,但从声音的角度来看,这将是整个“蠕虫”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.