Holen Sie sich die Klasse dieser definierten Methode

87

Wie kann ich die Klasse erhalten, die eine Methode in Python definiert hat?

Ich möchte, dass das folgende Beispiel " __main__.FooClass" druckt :

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

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)
Jesse Aldridge
quelle
Welche Version von Python verwenden Sie? Vor 2.2 konnten Sie im_class verwenden, dies wurde jedoch geändert, um den Typ des gebundenen Selbstobjekts anzuzeigen.
Kathy Van Stone
1
Gut zu wissen. Aber ich benutze 2.6.
Jesse Aldridge

Antworten:

69
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
Alex Martelli
quelle
1
Danke, ich hätte eine Weile gebraucht, um das selbst herauszufinden
David
Achtung, nicht alle Klassen implementieren __dict__! Manchmal __slots__wird verwendet. Es ist wahrscheinlich besser zu getattrtesten, ob die Methode in der Klasse ist.
Codie CodeMonkey
16
Für Python 3entnehmen Sie bitte diese Antwort .
Yoel
27
Ich 'function' object has no attribute 'im_class'
bekomme
5
In Python 2.7 funktioniert es nicht. Gleicher Fehler beim Fehlen von 'im_class'.
RedX
8

Vielen Dank an Sr2222 für den Hinweis, dass ich den Punkt verpasst habe ...

Hier ist der korrigierte Ansatz, der genau wie der von Alex ist, aber nichts importieren muss. Ich denke jedoch nicht, dass es eine Verbesserung ist, es sei denn, es gibt eine große Hierarchie geerbter Klassen, da dieser Ansatz stoppt, sobald die definierende Klasse gefunden wird, anstatt die gesamte Vererbung wie getmrogewohnt zurückzugeben. Wie gesagt, dies ist ein sehr unwahrscheinliches Szenario.

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

Und das Beispiel:

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

Die Alex-Lösung liefert die gleichen Ergebnisse. Solange der Alex-Ansatz verwendet werden kann, würde ich ihn anstelle dieses verwenden.

estani
quelle
Cls().meth.__self__gibt Ihnen nur die Instanz Clsdavon ist an diese bestimmte Instanz von gebunden meth. Es ist analog zu Cls().meth.im_class. Wenn Sie haben class SCls(Cls), SCls().meth.__self__erhalten Sie eine SClsInstanz, keine ClsInstanz. Was das OP will, ist zu bekommen Cls, was anscheinend nur durch Gehen des MRO verfügbar ist, wie es @Alex Martelli tut.
Silas Ray
@ sr2222 Du hast recht. Ich habe die Antwort geändert, da ich bereits begonnen habe, obwohl ich denke, dass die Alex-Lösung kompakter ist.
Estani
1
Es ist eine gute Lösung, wenn Sie Importe vermeiden müssen, aber da Sie die MRO im Grunde nur neu implementieren, ist nicht garantiert, dass sie für immer funktioniert. Das MRO wird wahrscheinlich gleich bleiben, aber es wurde bereits einmal in Pythons Vergangenheit geändert, und wenn es erneut geändert wird, führt dieser Code zu subtilen, allgegenwärtigen Fehlern.
Silas Ray
Immer wieder treten bei der Programmierung "sehr unwahrscheinliche Szenarien" auf. Selten Katastrophen verursachen. Im Allgemeinen ist das Denkmuster "XY.Z% es wird nie passieren" ein äußerst mieses Denkwerkzeug beim Codieren. Benutze es nicht. Schreiben Sie 100% korrekten Code.
Ulidtko
@ulidtko Ich denke du hast die Erklärung falsch verstanden. Es geht nicht um Korrektheit, sondern um Geschwindigkeit. Es gibt keine "perfekte" Lösung, die für alle Fälle geeignet ist, andernfalls gibt es beispielsweise nur einen Sortieralgorithmus. Die hier vorgeschlagene Lösung sollte im "seltenen" Fall schneller sein. Da die Geschwindigkeit in 99% aller Fälle nach der Lesbarkeit liegt, ist diese Lösung möglicherweise "nur" in diesem seltenen Fall eine bessere Lösung. Der Code, falls Sie ihn nicht gelesen haben, ist zu 100% korrekt, wenn Sie dies befürchtet haben.
Estani
6

Ich weiß nicht, warum niemand dies angesprochen hat oder warum die Top-Antwort 50 positive Stimmen hat, wenn es höllisch langsam ist, aber Sie können auch Folgendes tun:

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

Für Python 3 glaube ich, dass sich dies geändert hat und Sie müssen sich darum kümmern .__qualname__.

Nick Chapman
quelle
2
Hmm. Ich sehe die definierende Klasse nicht, wenn ich das in Python 2.7 mache - Ich bekomme die Klasse, für die die Methode aufgerufen wurde, nicht die, in der sie definiert ist ...
F1Rumors
5

Wenn Sie in Python 3 das eigentliche Klassenobjekt benötigen, können Sie Folgendes tun:

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

Wenn die Funktion zu einer verschachtelten Klasse gehören könnte, müssten Sie wie folgt iterieren:

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
Matthew D. Scholefield
quelle
1

Ich fing an, etwas Ähnliches zu tun. Im Grunde war die Idee zu überprüfen, wann eine Methode in einer Basisklasse implementiert wurde oder nicht in einer Unterklasse. Es stellte sich heraus, dass ich nicht feststellen konnte, wann eine Zwischenklasse die Methode tatsächlich implementierte.

Meine Problemumgehung war eigentlich recht einfach; ein Verfahren zum Einstellen Attributs und später seine Anwesenheit zu testen. Hier ist eine Vereinfachung des Ganzen:

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

UPDATE: Rufen Sie tatsächlich method()von auf run_method()(ist das nicht der Geist?) Und lassen Sie alle Argumente unverändert an die Methode übergeben.

PS: Diese Antwort beantwortet die Frage nicht direkt. IMHO gibt es zwei Gründe, warum man wissen möchte, welche Klasse eine Methode definiert; Erstens müssen Sie mit den Fingern auf eine Klasse im Debug-Code zeigen (z. B. bei der Ausnahmebehandlung), und zweitens müssen Sie feststellen, ob die Methode erneut implementiert wurde (wobei method ein Stub ist, der vom Programmierer implementiert werden soll). Diese Antwort löst diesen zweiten Fall auf andere Weise.

Thomas Guyot-Sionnest
quelle