Ich möchte eine Klasse in Python erstellen, mit der ich Attribute und Methoden hinzufügen und entfernen kann. Wie kann ich das erreichen?
Oh, und bitte frag nicht warum.
Ich möchte eine Klasse in Python erstellen, mit der ich Attribute und Methoden hinzufügen und entfernen kann. Wie kann ich das erreichen?
Oh, und bitte frag nicht warum.
Antworten:
Ich möchte eine Klasse in Python erstellen, mit der ich Attribute und Methoden hinzufügen und entfernen kann.
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'
Dieses Beispiel zeigt die Unterschiede zwischen dem Hinzufügen einer Methode zu einer Klasse und einer Instanz.
>>> 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'
Eine möglicherweise interessante Alternative zur Verwendung types.MethodType
in:
>>> f = types.MethodType(talk, puppy, Dog)
>>> puppy.talk = f # add method to specific instance
wäre, die Tatsache auszunutzen, dass Funktionen Deskriptoren sind :
>>> puppy.talk = talk.__get__(puppy, Dog)
Ich möchte eine Klasse in Python erstellen, mit der ich Attribute und Methoden hinzufügen und entfernen kann. Wie kann ich das erreichen?
Sie können jeder Klasse Attribute und Methoden hinzufügen und daraus entfernen, die allen Instanzen der Klasse zur Verfügung stehen:
>>> 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'
Sie können der Klasse einfach direkt zuweisen (entweder durch Zugriff auf den ursprünglichen Klassennamen oder über __class__
):
class a : pass
ob=a()
ob.__class__.blah=lambda self,k: (3, self,k)
ob.blah(5)
ob2=a()
ob2.blah(7)
wird gedruckt
(3, <__main__.a instance at 0x7f18e3c345f0>, 5)
(3, <__main__.a instance at 0x7f18e3c344d0>, 7)
eine weitere Alternative, wenn Sie die Klasse Großhandel ersetzen müssen , ist das zu ändern Klasse Attribut:
>>> 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
Einfach:
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
was in ... endet:
['__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
Muss die Klasse selbst unbedingt geändert werden? Oder ist das Ziel einfach zu ersetzen, was object.method () zu einem bestimmten Zeitpunkt zur Laufzeit tut?
Ich frage, weil ich das Problem, die Klasse tatsächlich zu modifizieren, um Affen-Patch-spezifische Methodenaufrufe in meinem Framework mit getattribute und einem Runtime Decorator auf meinem Base-Vererbungsobjekt zu ändern, umgehe .
Methoden, die von einem Basisobjekt in getattribute abgerufen werden, werden in einen Runtime_Decorator eingeschlossen, der die Methode analysiert und Schlüsselwortargumente aufruft, damit Dekoratoren / Affen-Patches angewendet werden.
Auf diese Weise können Sie die Syntax object.method (monkey_patch = "mypatch"), object.method (decorator = "mydecorator") und sogar object.method (decorators = my_decorator_list) verwenden.
Dies funktioniert für jeden einzelnen Methodenaufruf (ich lasse magische Methoden weg), ohne Klassen- / Instanzattribute zu ändern, kann beliebige, sogar fremde Methoden zum Patchen verwenden und funktioniert transparent bei Unterklassen, die von Base erben (sofern sie dies nicht tun) getattribute natürlich nicht überschreiben ).
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")
Der Nachteil dieses Ansatzes ist getAttribute Haken alle Zugriff auf Attribute, so dass die Überprüfung von und potenzielle Umwickeln von Methoden auch für Attribute auf, der nicht Methoden sind + nicht die Funktion für den bestimmten Anruf in Frage gestellt werden , nutzen. Und die Verwendung von getattribute ist von Natur aus etwas kompliziert.
Die tatsächlichen Auswirkungen dieses Overheads waren meiner Erfahrung nach / für meine Zwecke vernachlässigbar, und auf meiner Maschine wird ein Dual-Core-Celeron ausgeführt. Die vorherige Umsetzung verwendete ich introspected Methoden auf Objekt init und banden die Runtime_Decorator Verfahren dann. Auf diese Weise wurde die Verwendung von getattribute überflüssig und der zuvor erwähnte Overhead reduziert. Es bricht jedoch auch Gurke (möglicherweise nicht Dill) und ist weniger dynamisch als dieser Ansatz.
Die einzigen Anwendungsfälle, auf die ich mit dieser Technik tatsächlich "in the wild" gestoßen bin, waren Timing- und Tracing-Dekorateure. Die Möglichkeiten, die sich daraus ergeben, sind jedoch äußerst vielfältig.
Wenn Sie eine bereits vorhandene Klasse haben, die nicht von einer anderen Basis geerbt werden kann (oder die Technik verwenden, die ihre eigene Klassendefinition oder die Basisklasse enthält), dann trifft das Ganze leider überhaupt nicht auf Ihr Problem zu.
Ich denke nicht, dass das Festlegen / Entfernen nicht aufrufbarer Attribute für eine Klasse zur Laufzeit unbedingt so herausfordernd ist. es sei denn, Sie möchten, dass Klassen, die von der geänderten Klasse erben, automatisch auch die Änderungen in sich selbst widerspiegeln ... Das wäre jedoch eine ganze Reihe weiterer Würmer.