Python: Ändern von Methoden und Attributen zur Laufzeit

76

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.

Migol
quelle
5
Duplikat? stackoverflow.com/questions/972/…
Sasha Chedygov
3
Sie möchten herausfinden, wie man Enten in Python schlägt? en.wikipedia.org/wiki/Duck_punching
Baudtack
1
upvoted für die Frage, nicht zu fragen, warum
oulenz

Antworten:

47

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'
Unbekannt
quelle
9
Beachten Sie, dass dies SpecialClass eine Klassenmethode hinzufügt. Es wird keine Methode hinzugefügt, die für alle zukünftigen Instanzen von SpecialClass verfügbar sein wird. (I Wunder tun , wenn es einen Weg gibt , zu tun , dass .) _
M. Elkstein
Das wäre wirklich interessant.
Technillogue
123

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'
Paolo Bergantino
quelle
13
Beachten Sie, dass Sie dies nur für Klassen tun können , nicht für Instanzen . Wenn Sie pupy.talk = talk ausführen, ist talk keine "gebundene Methode", das heißt, es wird nicht das implizite "self" -Argument erhalten.
Paul Fisher
10
Um Pauls Kommentar zu ergänzen: Wenn Sie eine Instanzmethode monkeypatchen möchten: "import types; f = types.MethodType (talk, welpe, Dog); pupy.talk = f"
Jarret Hardie
5
+1 an Paolo, um den dynamischen Effekt des Zuweisens und Löschens von Klassenmethodenattributen zu demonstrieren.
Jarret Hardie
3
Vielen Dank für die tollen Kommentare, ich habe die Antwort aktualisiert, um die Unterschiede zu zeigen.
Paolo Bergantino
8
Sehr schön bearbeitetes Beispiel ... es sollte fast in die Python-API-Dokumente für das Typmodul passen, die absolut unzureichend sind.
Jarret Hardie
28

Eine möglicherweise interessante Alternative zur Verwendung types.MethodTypein:

>>> 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)
Alex Martelli
quelle
2
Ich habe gerade etwas gelernt :) Aber ich denke, dass es weniger lesbar aussieht.
Nicolas Dumazet
+1 Gute alternative Syntax, wie Sie sagen. Ich bin neugierig: Gibt es besondere Vorteile für diesen Ansatz oder für die Verwendung von "Typen"? Letztendlich produzieren sie das gleiche Ergebnis und interne Bindungen AFAICAT. Produziert types.MethodType effektiv einen Deskriptor oder ist mehr am Werk?
Jarret Hardie
@NicDumZ, ja, die __ Dinger sehen nie wirklich gut aus. @Jarret, irgendwann wurde in Python 3 lose über die Abschaffung des Moduls "Typen" gesprochen, aber es blieb von 37 Einträgen auf 12 reduziert (das "neue" Modul ging, yay! -). Semantisch sind sie wirklich gleich: MethodType gibt dieselbe Art von Objekt zurück, die das Ergebnis von get ist - eine Instanz von <type 'instancemethod'>.
Alex Martelli
5

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'
Robert Rossney
quelle
4

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)
Johan Lundberg
quelle
0

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
Lie Ryan
quelle
Interessant, aber es ging darum, Methoden zur Laufzeit zu ändern. Dies sieht aus wie eine Idee für IoC-Container mit Laufzeitumschaltung :)
Migol
Ja, mit der Switching-Klasse können Sie Methoden im Großhandel ändern, insbesondere wenn Sie dies mit der Tatsache hinzufügen, dass Python mehrere Vererbungen zulässt und Python-Klassen veränderbar sind. Dies kann zu ziemlich leistungsfähigen dynamischen Techniken oder sehr nicht wartbarem Code führen.
Lie Ryan
0

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
Entwickler
quelle
0

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.

Ella Rose
quelle