Python: cambiando i metodi e gli attributi in fase di runtime

Vorrei creare una classe in Python che posso aggiungere e rimuovere gli attributi e metodi. Come posso realizzazione che?

Oh, e per favore non chiedetemi il perchè.

InformationsquelleAutor Migol | 2009-06-07



8 Replies
  1. 45

    Vorrei creare una classe in Python che posso aggiungere e rimuovere gli attributi e metodi.

    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'
    • Si noti che questo aggiunge un metodo di classe a SpecialClass. Fa non aggiungere un metodo che sarà disponibile per tutti i futuri casi di SpecialClass. (Mi chiedo se c’è un modo per fare che.)_
    • Che sarebbe davvero interessante.
  2. 115

    Questo esempio mostra le differenze tra l’aggiunta di un metodo di una classe e di un’istanza.

    >>> 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'
    • Nota che si può fare solo questo classi, non le istanze. Se si fa cucciolo.talk = parlare, parlare non sarà un “metodo vincolato”, che è a dire, non l’implicito “self” argomento.
    • Per aumentare Paolo commento: se si desidera monkeypatch un metodo di istanza: “importare i tipi; f = tipi.MethodType(parlare, cucciolo, Cane); cucciolo.talk = f”
    • +1 a Paolo per dimostrare l’effetto dinamico di assegnazione e l’eliminazione di metodo di una classe attributi.
    • Grazie per i commenti ragazzi, ho aggiornato la risposta per mostrare le differenze.
    • Molto bello curato esempio… dovrebbe quasi andare in Python API docs per i tipi di modulo, che sono chiaramente inadeguati.
    • Questa era buona, ma combinato con l’Ignoto risposta mi ha dato quello di cui avevo bisogno.
    • Solo in casi mancanti sono: 1: Come il metodo talk viene rimosso dal puppy dopo che è stato aggiunto in modo dinamico. 2: Come il metodo talk viene rimosso da una sola istanza dopo che è stato aggiunto per Dog.
    • Trovo che non è possibile utilizzare super().talk() all’interno def talk(self):, c’è un modo per utilizzare super()?
    • Questo è cool! Mi tengo questo piccolo e grazioso trucco in mente

  3. 28

    Una possibile alternativa all’utilizzo di types.MethodType in:

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

    sarebbe quello di sfruttare il fatto che le funzioni sono descrittori:

    >>> puppy.talk = talk.__get__(puppy, Dog)
    • Ho appena imparato qualcosa :), Ma penso che non sembra meno leggibile.
    • +1 Buona alternativa sintassi, come dici tu. Sono curioso: ci sono particolari vantaggi di questo approccio, o all’uso di “tipi”? In definitiva, producono lo stesso risultato e interno di associazioni AFAICAT. Non tipi.MethodType efficace per produrre un descrittore, o c’è più lavoro?
    • sì, l’ __ thingies mai davvero guardare bene. @Jarret, c’era ad un certo punto di Python 3, del design sciolto parlare di abolire i “tipi” del modulo, ma è rimasto, alleggerita da 37 voci di 12 (il “nuovo” modulo ha fatto andare, yay!-). Semanticamente in realtà sono la stessa: MethodType restituisce lo stesso tipo di oggetto che è il risultato di get — un’istanza di <tipo ‘instancemethod’>.
  4. 5

    Vorrei creare una classe in Python che posso aggiungere e rimuovere gli attributi e metodi. Come posso realizzazione che?

    È possibile aggiungere e rimuovere gli attributi e i metodi di una classe, e sarà disponibile per tutte le istanze della 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. 4

    è possibile assegnare direttamente alla classe (o accedendo all’originale nome di classe o via __class__ ):

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

    stampa

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

    un’altra alternativa, se avete bisogno di sostituire la classe all’ingrosso è di modificare il classe attributo:

    >>> 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
    • Interessante, ma il punto era quello di modificare i metodi di @ runtime. Questo appare come un’idea per Cio contenitore con il runtime di commutazione 🙂
    • Sì, il passaggio di classe consente di modificare i metodi di vendita all’ingrosso, specialmente quando si aggiunge questo con il fatto che python permette l’ereditarietà multipla e le classi python sono mutabili, può portare a molto potenti tecniche dinamiche o molto non gestibile codice.
  7. 0

    Semplicemente:

    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

    che si traduce in:

    ['__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
  8. 0

    La classe di per sé necessariamente bisogno di essere modificato? O l’obiettivo è semplicemente di sostituire l’oggetto.metodo() non in un punto particolare durante la fase di esecuzione?

    Chiedo perché ho aggirare il problema di dover modificare la classe di scimmia patch specifiche chiamate di metodo nel mio quadro con getattribute e un Runtime Decoratore sul mio Base eredità oggetto.

    Metodi recuperati da un oggetto di Base in getattribute sono avvolti in un Runtime_Decorator che analizza le chiamate al metodo di parole chiave di argomenti per decoratori/scimmia patch da applicare.

    Questo consente di utilizzare la sintassi oggetto.metodo(monkey_patch=”mypatch”), oggetto.metodo(decorator=”mydecorator”), e anche l’oggetto.metodo(decoratori=my_decorator_list).

    Questo funziona per qualsiasi individuo chiamata di metodo (lascio fuori i metodi magici), senza in realtà la modifica di qualsiasi classe/attributi di istanza, può utilizzare arbitrario, anche straniere metodi di patch, e funziona in modo trasparente sublcasses che ereditano dalla Base (a condizione di non ignorare getattribute ovviamente).

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

    Lo svantaggio di questo approccio è getattribute ganci tutti l’accesso agli attributi, in modo che il controllo e il potenziale di avvolgimento di metodi si verifica anche per gli attributi che non sono metodi + non utilizza la funzione per la chiamata in questione. E l’utilizzo di getattribute a tutti è di per sé un po ‘ complicato.

    L’effettivo impatto di questo overhead nella mia esperienza e per i miei scopi è stato trascurabile, e la mia macchina esegue un dual core Celeron. La precedente realizzazione ho usato introspected metodi su oggetti init e legò il Runtime_Decorator per i metodi di allora. Fare le cose in quel modo ha eliminato la necessità di utilizzare getattribute e ridotto il sovraccarico accennato in precedenza,… tuttavia, si rompe anche pickle (forse non aneto) ed è meno dinamico, quindi questo approccio.

    Solo in casi di utilizzo ho effettivamente imbattuto “in the wild” con questa tecnica sono stati con i tempi e analisi dei decoratori. Tuttavia, le possibilità che si apre sono estremamente ampia.

    Se si dispone di una classe preesistente che non può essere fatto ereditare da una base diversa (o utilizzare la tecnica è proprio la definizione di classe o in classe di base’), poi il tutto, semplicemente, non si applicano a tutti purtroppo.

    Non credo impostazione/rimozione non callable attributi di una classe in fase di runtime è necessariamente così impegnativo? a meno che non si desidera che le classi che ereditano dalla classe modificata automaticamente per riflettere le modifiche in se stessi, come pure… sarebbe un intero ‘nother possono o’ vermi dal suono di esso, però.

Lascia un commento