web-dev-qa-db-fra.com

Python: modification des méthodes et des attributs à l'exécution

Je souhaite créer une classe en Python que je peux ajouter et supprimer des attributs et des méthodes. Comment puis-je accomplir cela?

Oh, et s'il vous plaît, ne demandez pas pourquoi.

68
Migol

Je souhaite créer une classe en Python que je peux ajouter et supprimer des attributs et des méthodes.

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

Cet exemple montre les différences entre l'ajout d'une méthode à une classe et une instance. 

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

Une alternative éventuellement intéressante à l’utilisation de types.MethodType dans:

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

serait d'exploiter le fait que les fonctions sont descripteurs :

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

Je souhaite créer une classe en Python que je peux ajouter et supprimer des attributs et des méthodes. Comment puis-je accomplir cela?

Vous pouvez ajouter et supprimer des attributs et des méthodes à n'importe quelle classe. Ils seront disponibles pour toutes les instances de la classe:

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

vous pouvez simplement assigner directement à la classe (soit en accédant au nom de la classe d'origine, soit via __class__):

class a : pass
ob=a()
ob.__class__.blah=lambda self,k: (3, self,k)
ob.blah(5)
ob2=a()
ob2.blah(7)

imprimera

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

Simplement: 

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

qui se traduit par: 

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

une autre alternative, si vous devez remplacer la classe en gros est de modifier l'attribut 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
0
Lie Ryan

La classe elle-même doit-elle nécessairement être modifiée? Ou bien le but est-il simplement de remplacer ce que object.method () fait à un moment donné pendant l'exécution? 

Je pose la question parce que j’évite le problème de la modification de la classe en un appel à une méthode spécifique à un correctif de singe dans mon cadre avec getattribute et un objet Runtime Decorator sur mon héritage Base. 

Les méthodes récupérées par un objet Base dans getattribute sont encapsulées dans un Runtime_Decorator qui analyse les arguments de la méthode appelle des arguments de mot-clé à appliquer par les correctifs décorators/monkey. 

Cela vous permet d'utiliser la syntaxe object.method (monkey_patch = "mypatch"), object.method (decorator = "mydecorator") et même object.method (decorators = my_decorator_list).

Cela fonctionne pour tout appel de méthode individuel (je laisse de côté les méthodes magiques), le fait sans modifier réellement les attributs de classe/instance, peut utiliser des méthodes arbitraires, même étrangères, pour patcher, et fonctionnera de manière transparente sur les sous-classes qui héritent de Base ne pas écraser getattribute bien sur).

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

L'inconvénient de cette approche est getattribute hooks all accès aux attributs, de sorte que la vérification et le wrapping potentiel des méthodes se produisent même pour des attributs qui ne sont pas des méthodes + n'utilisera pas la fonctionnalité pour l'appel en question Dans la question. Et utiliser getattribute est compliqué par nature. 

L’impact réel de cette surcharge selon mon expérience/mes objectifs a été négligeable, et ma machine utilise un Celeron à double cœur. L’implémentation précédente, j’utilisais des méthodes inspectées lorsqu’un objet init était lié à Runtime_Decorator puis à des méthodes. Faire les choses de cette façon a éliminé le besoin d'utiliser getattribute et a réduit les frais généraux mentionnés précédemment ... Cependant, cela casse aussi les traces (peut-être pas de l'aneth) et est moins dynamique que cette approche.

Les seuls cas d'utilisation que j'ai rencontrés "à l'état sauvage" avec cette technique concernent les décorateurs de chronométrage et de traçage. Cependant, les possibilités qu’il ouvre sont extrêmement variées.

Si vous avez une classe préexistante qui ne peut pas hériter d'une base différente (ou utilisez la technique c'est sa propre définition de classe ou dans sa classe de base '), alors le problème ne s'applique tout simplement pas à votre problème, malheureusement.

Je ne pense pas que définir/supprimer des attributs non appelables sur une classe au moment de l'exécution pose nécessairement autant de problèmes? à moins que vous ne vouliez que les classes héritées de la classe modifiée reflètent automatiquement les modifications en elles-mêmes ... Ce serait un tout autre savoir.

0
Ella Rose