Ottieni classe quel metodo definito


89

Come posso ottenere la classe che ha definito un metodo in Python?

Vorrei che il seguente esempio stampasse " __main__.FooClass":

class FooClass:
    def foo_method(self):
        print "foo"

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)

Quale versione di Python stai usando? Prima della 2.2 si poteva usare im_class, ma è stato cambiato per mostrare il tipo di oggetto self associato.
Kathy Van Stone,

1
Buono a sapersi. Ma sto usando 2.6.
Jesse Aldridge,

Risposte:


70
import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None

1
Grazie, mi ci sarebbe voluto un po 'per capirlo da solo
David

Attenzione, non tutte le classi implementano __dict__! A volte __slots__viene utilizzato. Probabilmente è meglio usare getattrper verificare se il metodo è nella classe.
Codie CodeMonkey

16
Per Python 3favore, fare riferimento a questa risposta .
Yoel

27
Ricevo:'function' object has no attribute 'im_class'
Zitrax

5
In Python 2.7 non funziona. Stesso errore sulla mancanza di "im_class".
RedX

8

Grazie Sr2222 per aver sottolineato che mi mancava il punto ...

Ecco l'approccio corretto che è proprio come quello di Alex ma non richiede di importare nulla. Non penso che sia un miglioramento, a meno che non ci sia un'enorme gerarchia di classi ereditate poiché questo approccio si interrompe non appena viene trovata la classe di definizione, invece di restituire l'intera eredità come getmrofa. Come detto, questo è uno scenario molto improbabile.

def get_class_that_defined_method(method):
    method_name = method.__name__
    if method.__self__:    
        classes = [method.__self__.__class__]
    else:
        #unbound method
        classes = [method.im_class]
    while classes:
        c = classes.pop()
        if method_name in c.__dict__:
            return c
        else:
            classes = list(c.__bases__) + classes
    return None

E l'esempio:

>>> class A(object):
...     def test(self): pass
>>> class B(A): pass
>>> class C(B): pass
>>> class D(A):
...     def test(self): print 1
>>> class E(D,C): pass

>>> get_class_that_defined_method(A().test)
<class '__main__.A'>
>>> get_class_that_defined_method(A.test)
<class '__main__.A'>
>>> get_class_that_defined_method(B.test)
<class '__main__.A'>
>>> get_class_that_defined_method(C.test)
<class '__main__.A'>
>>> get_class_that_defined_method(D.test)
<class '__main__.D'>
>>> get_class_that_defined_method(E().test)
<class '__main__.D'>
>>> get_class_that_defined_method(E.test)
<class '__main__.D'>
>>> E().test()
1

La soluzione Alex restituisce gli stessi risultati. Finché l'approccio di Alex può essere utilizzato, lo userei al posto di questo.


Cls().meth.__self__ti dà solo l'istanza di Clsche è legata a quella specifica istanza di meth. È analogo a Cls().meth.im_class. Se lo hai class SCls(Cls), SCls().meth.__self__otterrai SClsun'istanza, non Clsun'istanza. Ciò che l'OP vuole è ottenere Cls, che sembra sia disponibile solo camminando sull'MRO come fa @Alex Martelli.
Silas Ray

@ sr2222 Hai ragione. Ho modificato la risposta poiché ho già iniziato anche se penso che la soluzione di Alex sia più compatta.
estani

1
È una buona soluzione se devi evitare le importazioni, ma poiché in pratica stai solo reimplementando l'MRO, non è garantito che funzioni per sempre. L'MRO probabilmente rimarrà lo stesso, ma è già stato modificato una volta nel passato di Python, e se viene modificato di nuovo, questo codice risulterà in bug sottili e pervasivi.
Silas Ray

Di volta in volta, nella programmazione si verificano "scenari molto improbabili". Di rado, provocando disastri. In generale, il modello di pensiero "XY.Z% non accadrà mai" è uno strumento di pensiero estremamente schifoso quando si fa codice. Non usarlo. Scrivi il codice corretto al 100%.
ulidtko

@ulidtko Penso che tu abbia letto male la spiegazione. Non si tratta di correttezza ma di velocità. Non esiste una soluzione "perfetta" che si adatti a tutti i casi, altrimenti ad es. Ci sarà solo un algoritmo di ordinamento. La soluzione qui proposta dovrebbe essere più veloce nel caso "raro". Poiché la velocità è pari al 99% di tutti i casi dopo la leggibilità, questa soluzione potrebbe essere una soluzione migliore "solo" in quel raro caso. Il codice, nel caso in cui non lo avessi letto, è corretto al 100%, se era quello che temevi.
estani

6

Non so perché nessuno lo abbia mai sollevato o perché la risposta principale ha 50 voti positivi quando è lento come l'inferno, ma puoi anche fare quanto segue:

def get_class_that_defined_method(meth):
    return meth.im_class.__name__

Per Python 3 credo che questo sia cambiato e dovrai esaminare .__qualname__.


2
Hmm. Non vedo la classe di definizione quando lo faccio in Python 2.7 - Ricevo la classe su cui è stato chiamato il metodo, non quella in cui è definito ...
F1Rumors

5

In Python 3, se hai bisogno dell'effettivo oggetto di classe puoi fare:

import sys
f = Foo.my_function
vars(sys.modules[f.__module__])[f.__qualname__.split('.')[0]]  # Gets Foo object

Se la funzione potesse appartenere a una classe annidata, sarebbe necessario iterare come segue:

f = Foo.Bar.my_function
vals = vars(sys.modules[f.__module__])
for attr in f.__qualname__.split('.')[:-1]:
    vals = vals[attr]
# vals is now the class Foo.Bar

2

Python 3

Risolto in un modo molto semplice:

str(bar.foo_method).split(" ", 3)[-2]

Questo da

'FooClass.foo_method'

Dividi in punto per ottenere separatamente la classe e il nome della funzione


Wow, che risparmio!
user3712978

1

Ho iniziato a fare qualcosa di simile, fondamentalmente l'idea era controllare ogni volta che un metodo in una classe base era stato implementato o meno in una sottoclasse. Nel modo in cui l'ho fatto originariamente, non sono riuscito a rilevare quando una classe intermedia stava effettivamente implementando il metodo.

La mia soluzione alternativa era abbastanza semplice in realtà; impostare un attributo di metodo e verificarne la presenza in seguito. Ecco una semplificazione dell'intera faccenda:

class A():
    def method(self):
        pass
    method._orig = None # This attribute will be gone once the method is implemented

    def run_method(self, *args, **kwargs):
        if hasattr(self.method, '_orig'):
            raise Exception('method not implemented')
        self.method(*args, **kwargs)

class B(A):
    pass

class C(B):
    def method(self):
        pass

class D(C):
    pass

B().run_method() # ==> Raises Exception: method not implemented
C().run_method() # OK
D().run_method() # OK

AGGIORNAMENTO: Effettivamente chiama method()darun_method() (non è quello lo spirito?) E fa passare tutti gli argomenti non modificati al metodo.

PS: questa risposta non risponde direttamente alla domanda. IMHO ci sono due ragioni per cui si vorrebbe sapere quale classe ha definito un metodo; il primo è puntare il dito su una classe nel codice di debug (come nella gestione delle eccezioni), e il secondo è determinare se il metodo è stato reimplementato (dove il metodo è uno stub pensato per essere implementato dal programmatore). Questa risposta risolve il secondo caso in modo diverso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.