【问题标题】:Python: changing methods and attributes at runtimePython:在运行时更改方法和属性
【发布时间】:2010-11-01 01:38:34
【问题描述】:

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

哦,请不要问为什么。

【问题讨论】:

标签: python reflection runtime


【解决方案1】:

这个例子展示了将方法添加到类和实例之间的区别。

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

【讨论】:

  • 请注意,您只能对 classes 执行此操作,而不能对 instances 执行此操作。如果你做puppy.talk = talk,talk就不会是一个“绑定方法”,也就是说,它不会得到隐含的“self”参数。
  • 补充 Paul 的评论:如果您希望对实例方法进行猴子补丁:“import types; f = types.MethodType(talk,uppup, Dog);uppup.talk = f"
  • +1 给 Paolo,用于演示分配和删除类方法属性的动态效果。
  • 感谢伟大的 cmets 家伙,我更新了答案以显示差异。
  • 非常好的编辑示例......它几乎应该出现在类型模块的 Python API 文档中,这非常不足。
【解决方案2】:

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

我问是因为我回避了实际修改类以使用 getattribute 和我的 Base 继承对象上的运行时装饰器在我的框架中修补特定方法调用的问题。

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

这使您能够利用语法 object.method(monkey_patch="mypatch")、object.method(decorator="mydecorator"),甚至 object.method(decorators=my_decorator_list)。

这适用于任何单独的方法调用(我省略了魔术方法),这样做无需实际修改任何类/实例属性,可以使用任意甚至外来方法进行修补,并且可以透明地在继承自 Base 的子类上工作(当然,前提是它们不覆盖 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 挂钩 all 对属性的访问,因此即使对于不是方法的属性也会进行方法的检查和潜在的包装t 将该功能用于相关的特定呼叫。而使用 getattribute 本身就有些复杂。

这种开销对我的经验/对我的目的的实际影响可以忽略不计,我的机器运行双核赛扬。之前的实现我在对象 init 上使用了自省方法,然后将 Runtime_Decorator 绑定到方法。这样做消除了使用 getattribute 的需要并减少了前面提到的开销......但是,它也破坏了 pickle(可能不是 dill)并且不如这种方法动态。

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

如果您有一个无法从不同的基类继承的预先存在的类(或利用它自己的类定义或在它的基类中的技术),那么不幸的是,整个事情根本不适用于您的问题.

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      简单地说:

      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
      

      【讨论】:

        【解决方案5】:

        另一种选择,如果需要更换类批发就是修改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
        

        【讨论】:

        • 有趣,但重点是修改方法@runtime。这看起来像是带有运行时切换的 IoC 容器的一个想法 :)
        • 是的,切换类允许你批量修改方法,特别是当你添加这个,因为python允许多重继承并且python类是可变的,它可能导致一些非常强大的动态技术或非常不可维护代码。
        【解决方案6】:

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

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

        将利用函数是descriptors的事实:

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

        【讨论】:

        • 我刚刚学到了一些东西 :) 但我认为它看起来不太可读。
        • +1 正如您所说,很好的替代语法。我很好奇:这种方法或使用“类型”有什么特别的好处吗?最终,它们产生相同的结果和内部绑定 AFAICAT。 types.MethodType 是否有效地产生了一个描述符,还是有更多的工作?
        • @NicDumZ,是的,__ 东西从来都不是很好看。 @Jarret,在 Python 3 的设计中,有过关于废除“类型”模块的松散讨论,但它仍然存在,从 37 个条目减少到 12 个(“新”模块确实消失了,耶!-)。从语义上讲,它们实际上是相同的:MethodType 返回与 get 的结果相同的对象—— 的实例。
        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

          • 请注意,这会为 SpecialClass 添加一个类方法。它确实添加了一个可供所有未来SpecialClass 实例使用的方法。 (我想知道是否有办法做到那个。)_
          • 会很有趣。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-26
          • 2017-03-17
          • 2013-02-14
          • 2011-09-20
          相关资源
          最近更新 更多