it-swarm.com.de

Python: Ändern von Methoden und Attributen zur Laufzeit

Ich möchte in Python eine Klasse erstellen, mit der ich Attribute und Methoden hinzufügen und entfernen kann. Wie kann ich das abschließen?

Oh, und bitte frag nicht warum.

68
Migol

Ich möchte in Python eine Klasse 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'
43
Unknown

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

Eine möglicherweise interessante Alternative zur Verwendung von types.MethodType in:

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

wäre die Tatsache zu nutzen, dass Funktionen Deskriptoren sind :

>>> puppy.talk = talk.__get__(puppy, Dog)
27
Alex Martelli

Ich möchte in Python eine Klasse erstellen, mit der ich Attribute und Methoden hinzufügen und entfernen kann. Wie kann ich das abschließen?

Sie können jeder Klasse Attribute und Methoden hinzufügen und entfernen. Sie stehen allen Instanzen der Klasse zur Verfügung:

>>> 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'
5
Robert Rossney

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 drucken

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

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 ... resultiert: 

['__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
Developer

eine andere Alternative besteht darin, das Attribut class zu ändern, wenn Sie die Klasse wholesale ersetzen müssen:

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

Muss die Klasse selbst notwendigerweise modifiziert werden? Oder ist es das Ziel, einfach zu ersetzen, was object.method () zu einem bestimmten Zeitpunkt zur Laufzeit tut? 

Ich frage, weil ich das Problem umgehen möchte, die Klasse tatsächlich zu ändern, um spezifische Methodenaufrufe in meinem Framework mit getattribute und einem Runtime Decorator für mein Basis-Vererbungsobjekt zu modifizieren. 

Methoden, die von einem Basisobjekt in getattribute abgerufen werden, werden in einen Runtime_Decorator eingeschlossen, der die Methode aufruft, um Schlüsselwortargumente aufzurufen, damit Patchworks von decorators/monkey angewendet werden. 

Damit 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 keine magischen Methoden aus), tut dies, ohne irgendwelche Klassen-/Instanzattribute zu modifizieren, kann beliebige, auch fremde Methoden zum Patchen verwenden, und wird transparent für Unterklassen funktionieren, die von Base erben (sofern sie dies nicht zulassen 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 der Zugriff auf getattribute hooks all auf Attribute, sodass das Überprüfen und das mögliche Umschließen von Methoden auch für Attribute erfolgt, die keine Methoden sind + die Funktion für den jeweiligen Aufruf nicht verwenden fraglich. Die Verwendung von getattribute ist an sich etwas kompliziert. 

Die tatsächlichen Auswirkungen dieses Overheads auf meine Erfahrung/für meine Zwecke waren vernachlässigbar, und meine Maschine verfügt über einen Dual-Core-Celeron. Bei der vorherigen Implementierung habe ich introspected Methoden für object init verwendet und den Runtime_Decorator dann an Methoden gebunden. Durch diese Vorgehensweise wurde die Verwendung von getattribute eliminiert, und der zuvor erwähnte Overhead wurde reduziert. Allerdings bricht es auch Pickle (möglicherweise nicht Dill) und ist weniger dynamisch als dieser Ansatz.

Die einzigen Anwendungsfälle, die mir bei dieser Technik "in der Wildnis" begegnet sind, waren Timing- und Tracker-Dekorateure. Die Möglichkeiten, die sich daraus ergeben, sind jedoch sehr vielfältig.

Wenn Sie über eine bereits vorhandene Klasse verfügen, die nicht von einer anderen Basis geerbt werden kann (oder die Technik als eigene Klassendefinition oder in ihrer Basisklasse verwenden), gilt das Ganze leider nicht für Ihr Problem.

Das Aufrufen/Entfernen nicht aufrufbarer Attribute für eine Klasse zur Laufzeit ist nicht unbedingt notwendig. Es sei denn, Sie möchten, dass Klassen, die von der modifizierten Klasse erben, auch automatisch die Änderungen in sich selbst widerspiegeln ... Das wäre ein ganz anderer 'Nother kann Würmer' durch den Klang.

0
Ella Rose