2012-12-12 17 views
10

Update - 2012/12/13dinamicamente aggiungendo metodi con o senza metaclasse

Giusto per chiarire - io non sono tanto interessati a modi come aggiungere metodi alle classi - come potete vedere qui sotto nella mia domanda e nelle risposte delle persone, c'è più di un modo per farlo (lingua in bocca e punta di cappello al mio io Perl).

La cosa a cui sono interessato è imparare qual è la differenza fondamentale di aggiungere metodi alle classi utilizzando approcci diversi e la grande domanda è davvero il motivo per cui ho bisogno di utilizzare i metaclassi per. Per esempio A Primer on Python Metaclass Programming afferma che:

Forse l'uso più comune di metaclassi [...]: aggiungere, cancellare, rinominare, o sostituendo i metodi per quelli definiti nella classe prodotta.

E dal momento che ci sono altri modi per farlo, sono perplesso e in cerca di spiegazioni.

Grazie!

originale - 2012/12/12

ho bisogno di aggiungere dinamicamente i metodi di una classe (e le classi di nuova generate sulla base di quella classe). Ho trovato due approcci, uno che riguarda la metaclasse, l'altro uno senza. Non riesco a vedere alcuna differenza in quello che i due approcci danno me a parte il fatto quest'ultimo non comporta la metaclasse "magia nera";)

Approccio # 1con metaclasse:

class Meta(type): 
     def __init__(cls, *args, **kwargs): 
       setattr(cls, "foo", lambda self: "[email protected]%s(class %s)" % (self, 
         cls.__name__)) 

class Y(object): 
     __metaclass__ = Meta 

y = Y() 
y.foo() # Gives '[email protected]<__main__.Y object at 0x10e4afd10>(class Y)' 

approccio # 2senza metaclasse:

class Z(object): 
     def __init__(self): 
       setattr(self.__class__, "foo", 
         lambda self: "[email protected]%s(class %s)" % 
         (self, self.__class__.__name__)) 

z = Z() 
z.foo() # Gives '[email protected]<__main__.Z object at 0x10c865dd0>(class Z)' 

Per quanto posso dillo, entrambi gli approcci mi danno gli stessi risultati e "espressività". Anche quando provo a creare una nuova classe usando type("NewClassY", (Y,), {}) o type("NewClassZ", (Z,), {}) ottengo gli stessi risultati attesi che non differiscono tra i due approcci.

Quindi, mi chiedo se c'è davvero nessuna differenza "sottostante" negli approcci, o se c'è qualcosa che vi "morso" me più tardi se ho usato sia # 1 o # 2 o se è solo uno zucchero sintattico?

PS: Sì, ho letto gli altri thread qui parlando di metaclassi in Python e modello di dati pythonic.

+0

Puoi chiamare 'Y.foo' e' Z.pippo' usando entrambi gli approcci? – Blender

+2

La differenza è che il metaclass creerà i metodi al momento della definizione della classe e '__init__'way creerà i metodi quando istanziate la classe. Esiste anche un'ereditarietà multipla (mixins) che è possibile utilizzare per aggiungere metodi. –

+0

@Blender No, non puoi chiamarli così poiché non sono "metodi di classe". – TomasHeran

risposta

2

Se il problema è l'aggiunta di metodi in modo dinamico, python lo gestisce in modo abbastanza diretto. Essa è la seguente:

#!/usr/bin/python 
class Alpha(): 

    def __init__(self): 
     self.a = 10 
     self.b = 100 

alpha = Alpha() 
print alpha.a 
print alpha.b 

def foo(self): 
    print self.a * self.b * self.c 

Alpha.c = 1000 
Alpha.d = foo 

beta = Alpha() 
beta.d() 

uscita:

$ python script.py 
10 
100 
1000000 

saluti!

P.S.: Non vedo alcuna somiglianza di magia nera qui (:

Edit:

Considerando il commento di Martineau, sto aggiungendo data attributes, non method function attributes davvero riesco a vedere alcuna differenza tra il fare Alpha.d = foo e Alpha.d = lambda self: foo(self), salvo che. sarei usando una funzione lambda come involucro per la funzione foo aggiunti

l'aggiunta del metodo è la stessa, e pitone stessa nominare entrambe aggiunte stessa:.

#!/usr/bin/python 
class Alpha(): 

    def __init__(self): 
     self.a = 10 
     self.b = 100 

alpha = Alpha() 
print alpha.a 
print alpha.b 

def foo(self): 
    print self.a * self.b * self.c 

Alpha.c = 1000 

Alpha.d = lambda self: foo(self) 
Alpha.e = foo 

print Alpha.d 
print Alpha.e 

a = Alpha() 
a.d() 
a.e() 

uscita:

10 
100 
<unbound method Alpha.<lambda>> 
<unbound method Alpha.foo> 
1000000 
1000000 

Come mostrato, pitone stesso nome entrambi aggiunte risultanti come metodi - l'unica differenza è che uno è un riferimento per funzionare foo, e l'altro è un riferimento a una funzione lambda utilizza la funzione foo nel suo corpo di definizione.

Se ho detto qualcosa di sbagliato, per favore, correggimi.

Saluti!

+0

La risposta non aggiunge metodi alla classe, ma aggiunge solo attributi dati, non attributi funzione metodo. – martineau

+0

ti dispiacerebbe dare un esempio di cosa sia un attributo 'method function'? – Rubens

+1

'Alpha.answer = lambda self: 42' – martineau

9

L'ovvia ragione per utilizzare i metaclassi è perché forniscono realmente metadata sulla classe non appena la classe è nota, non correlata alla presenza di oggetti o meno. Banale, vero? Beh, mostriamo alcuni comandi ho eseguito sui vostri originali Z e Y classi per vedere che cosa questo significa:

In [287]: hasattr(Y,'foo') 
Out[287]: True 

In [288]: hasattr(Z,'foo') 
Out[288]: False 

In [289]: Y.__dict__ 
Out[289]: 
<dictproxy {..., 'foo': <function __main__.<lambda>>}> 

In [290]: Z.__dict__ 
Out[290]: 
<dictproxy {...}> 

In [291]: z= Z() 

In [292]: hasattr(Z,'foo') 
Out[292]: True 

In [293]: Z.__dict__ 
Out[293]: 
<dictproxy {..., 'foo': <function __main__.<lambda>>}> 

In [294]: y = Y() 

In [295]: hasattr(Y,'foo') 
Out[295]: True 

In [296]: Y.__dict__ 
Out[296]: 
<dictproxy {..., 'foo': <function __main__.<lambda>>}> 

Come si può vedere la seconda versione in realtà altera la classe Z in modo significativo dopo che è stato dichiarato, qualcosa che spesso piace evitare. Non è certamente un'operazione insolita per Python eseguire operazioni su 'tipi' (oggetti di classe) e probabilmente vuoi che siano il più coerenti possibile, certamente per questo caso (dove il metodo non è realmente dinamico in fase di esecuzione, solo alla dichiarazione tempo).

Un'applicazione che salta alla mente è la documenazione. Se dovessi aggiungere una docstring a foo utilizzando il metaclasse, la documentazione potrebbe raccoglierla, tramite il metodo __init__ questo è molto improbabile.

Potrebbe anche portare a bug difficili da individuare. considera un pezzo di codice che utilizza la metainformazione di una classe. Potrebbe essere che nel 99,99% dei casi questo viene eseguito dopo che un'istanza di Z è già stata creata, ma che lo 0,01% può portare a comportamenti strani, se non si verifica un arresto anomalo.

Potrebbe anche essere complicato nelle catene di gerarchia, dove dovresti fare attenzione a dove chiamare il costruttore del genitore. Una classe in questo modo per esempio potrebbe dare problemi:

class Zd(Z): 
    def __init__(self): 
     self.foo() 
     Z.__init__(self) 
a = Zd() 
... 
AttributeError: 'Zd' object has no attribute 'foo' 

Anche se questo funziona bene:

class Yd(Y): 
    def __init__(self): 
     self.foo() 
     Y.__init__(self) 
a = Yd() 

Potrebbe sembrare stupido per chiamare un metodo senza il relativo __init__ in uso, ma può succedere quando si' Non stai attento, e certamente in gerarchie più complesse dove la MRO non è immediatamente ovvia. Ed è difficile individuare questo errore perché molto spesso Zd() avrà successo non appena Z.__init__ è stato chiamato da qualche parte prima.

+0

Grazie! Questo è esattamente quello che stavo cercando. – TomasHeran

Problemi correlati