2009-06-07 52 views
67

我想創建一個Python類,我可以添加和刪除屬性和方法。我怎樣才能做到這一點?Python:在運行時改變方法和屬性

哦,請不要問爲什麼。

+5

重複? http://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object – 2009-06-07 22:46:31

+3

你想知道如何在Python中做鴨子衝孔? http://en.wikipedia.org/wiki/Duck_punching – baudtack 2009-06-08 02:04:05

+0

upvoted for asked not to ask why – oulenz 2017-11-05 21:21:48

回答

42

我希望在創建一個類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' 
110

此示例顯示將方法添加到類和實例之間的差異。

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

請注意,您只能對* classes *,而不是* instances *執行此操作。如果你做puppy.talk =說話,說話不會是一種「約束方法」,也就是說,它不會得到隱含的「自我」論證。 – 2009-06-07 22:48:04

+8

爲了增強Paul的評論:如果您想要對實例方法進行monkeypatch:「import types; f = types.MethodType(talk,puppy,Dog); puppy.talk = f」 – 2009-06-07 23:09:52

27

A到使用types.MethodType可能有趣的選擇:

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

將利用一個事實,即功能descriptors

>>> puppy.talk = talk.__get__(puppy, Dog) 
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' 
0

另一種選擇,如果你需要更換類是批發修改屬性:

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

該類本身是否需要修改?或者僅僅是爲了替換object.method()在運行時的某個特定位置做什麼?

我問,因爲我回避了實際修改類的問題,在我的框架中使用了getattribute和我的Base繼承對象上的運行時裝飾器。

基地對象檢索到的方法getattribute包裝在一個Runtime_Decorator中,該Runtime_Decorator分析要應用的裝飾器/猴子修補程序的方法調用關鍵字參數。

這使您能夠利用語法object.method(monkey_patch =「mypatch」),object。方法(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所有訪問屬性,所以的檢查和方法潛在的包裝甚至未方法+不會被利用特徵的屬性發生有問題的特定電話。而使用getattribute本質上有點複雜。

這個開銷在我的經驗/我的目的中的實際影響可以忽略不計,而且我的機器運行雙核賽揚。之前的實現我在對象init上使用內省方法,然後將Runtime_Decorator綁定到方法。做這樣的事情消除了利用getattribute並減少了前面提到的開銷......但是,它也打破了醃菜(也許不是蒔蘿),並且不那麼動態,那麼這種方法。

我實際遇到的「在野外」使用這種技術的唯一用例是定時和跟蹤裝飾器。然而,它開啓的可能性非常廣泛。

如果你有一個先前存在的類不能從另一個基類繼承(或者利用它自己的類定義或者它的基類中的技術),那麼整個事情根本就不適用於你的問題不幸。

我不認爲在運行時設置/刪除類中的不可調用屬性是非常具有挑戰性的嗎?除非你想要從修改後的類繼承的類自動反映它們自身的變化......雖然這聽起來似乎是一個完整的「不可能的蠕蟲」。