2009-06-07 15 views
67

Deseo crear una clase en Python que pueda agregar y eliminar atributos y métodos. ¿Cómo puedo lograr eso?Python: cambio de métodos y atributos en el tiempo de ejecución

Ah, y por favor no preguntes por qué.

+5

¿Duplicado? http://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object –

+3

¿Quieres saber cómo hacer pato en python? http://en.wikipedia.org/wiki/Duck_punching – baudtack

+0

upvoted por preguntar para no preguntar por qué – oulenz

Respuesta

42

deseo de crear una clase en Python que puedo agregar y eliminar atributos y métodos.

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

Tenga en cuenta que esto agrega un método de clase a SpecialClass. * No * agrega un método que estará disponible para todas las instancias futuras de SpecialClass. (Me pregunto si hay una manera de hacerlo * que *.) _ –

+0

_ Eso sería realmente interesante. – Glycan

110

Este ejemplo muestra las diferencias entre agregar un método a una clase y a una instancia.

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

Tenga en cuenta que solo puede hacer esto a * classes *, not * instances *. Si lo haces puppy.talk = talk, talk no será un "método vinculado", es decir, no obtendrá el argumento "self" implícito. –

+8

Para aumentar el comentario de Paul: si desea aplicar un método de instancia de monkeypatch: "tipos de importación; f = types.MethodType (talk, puppy, Dog); puppy.talk = f" –

+5

+1 a Paolo para demostrar el efecto dinámico de la asignación y eliminar los atributos del método de clase. –

27

Una alternativa posiblemente interesante para el uso types.MethodType en:

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

sería aprovechar el hecho de que las funciones son descriptors:

>>> puppy.talk = talk.__get__(puppy, Dog) 
+2

Acabo de enterarme de algo :) Pero creo que parece menos legible. – NicDumZ

+0

+1 Buena sintaxis alternativa, como dices.Tengo curiosidad: ¿hay algún beneficio particular para este enfoque, o para usar "tipos"? En definitiva, producen el mismo resultado y enlaces internos AFAICAT. Does types.MethodType efectivamente produce un descriptor, o hay más en el trabajo? –

+0

@NicDumZ, sí, las cosas __ nunca se ven realmente bien. @Jarret, hubo en algún momento del diseño de Python 3 charlas sueltas sobre la abolición del módulo 'tipos', pero se mantuvo, adelgazado de 37 entradas a 12 (el 'nuevo' módulo se fue, ¡sí! -). Semánticamente son realmente iguales: MethodType devuelve el mismo tipo de objeto que es el resultado de __get__ - una instancia de . –

5

deseo de crear una clase en Python que puedo añadir y eliminar atributos y métodos. ¿Cómo puedo lograr eso?

Puede añadir y eliminar atributos y métodos de cualquier clase, y que estará disponible para todas las instancias de la clase:

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

otra alternativa, si es necesario sustituir la clase es por mayor para modificar la clase atributo:

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

Interesante, pero el objetivo era modificar methods @ runtime. Esto parece una idea para contenedor IoC con conmutación en tiempo de ejecución :) – Migol

+0

Sí, la clase de conmutación le permite modificar métodos al por mayor, especialmente cuando se agrega esto con el hecho de que python permite herencia múltiple y las clases de python son mutables, puede conducir a poderosas técnicas dinámicas o código muy poco sostenible. –

0

Simplemente:

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 

que se traduce en:

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

sólo se puede asignar directamente a la clase (o bien accediendo al nombre de la clase original o mediante __class__):

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

imprimirá

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

¿La clase en sí misma necesita ser modificada? ¿O el objetivo es simplemente reemplazar lo que object.method() hace en un punto particular durante el tiempo de ejecución?

Lo pregunto porque evito el problema de modificar realmente las llamadas a métodos específicos de parche de clase a mono en mi marco con getattribute y un Decodificador de tiempo de ejecución en mi objeto Base de herencia.

Métodos recuperados por un objeto Base de getAttribute se envuelven en una Runtime_Decorator que analiza el método llama argumentos clave para decoradores/mono parches a aplicar.

Esto le permite utilizar la sintaxis object.method (monkey_patch = "mypatch"), objeto.método (decorator = "mydecorator") e incluso object.method (decorators = my_decorator_list).

Esto funciona para cualquier llamada de método individual (dejo los métodos de magia), lo hace sin modificar ningún atributo de clase/instancia, puede utilizar métodos arbitrarios, incluso ajenos al parche, y funcionará transparentemente en subcatas que heredan de Base (siempre que no anulen getattribute, por supuesto).

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

La desventaja de este enfoque es getAttribute ganchos todo acceso a los atributos, por lo que la comprobación de envoltura y el potencial de los métodos ocurre incluso para atributos que no son métodos + no serán Utilizando la función de la llamada particular en cuestión. Y usar getattribute en absoluto es algo complicado.

El impacto real de esta sobrecarga en mi experiencia/para mis propósitos ha sido insignificante, y mi máquina funciona con un Celeron de doble núcleo. La implementación anterior utilicé métodos introspectados en el objeto init y luego vinculé el Runtime_Decorator a los métodos. Hacer las cosas de esa manera eliminó la necesidad de utilizar getattribute y redujo los gastos generales mencionados anteriormente ... sin embargo, también se rompe pickle (tal vez no eneldo) y es menos dinámico que este enfoque.

Los únicos casos de uso que he encontrado en la vida real con esta técnica fueron los decoradores de tiempo y trazado. Sin embargo, las posibilidades que abre son extremadamente amplias.

Si tiene una clase preexistente que no puede heredar desde una base diferente (o utiliza la técnica su propia definición de clase o en su clase base '), entonces todo el asunto simplemente no se aplica a su problema en absoluto Desafortunadamente.

No creo que establecer/eliminar atributos no llamables en una clase en tiempo de ejecución sea necesariamente tan desafiante? a menos que desee que las clases heredadas de la clase modificada también reflejen automáticamente los cambios en sí mismas ... Sin embargo, eso podría ser un conjunto de gusanos.

Cuestiones relacionadas