Pobierz klasę, która zdefiniowała metodę

89

Jak mogę uzyskać klasę, która zdefiniowała metodę w Pythonie?

Chciałbym, aby następujący przykład wyświetlał „ __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)
Jesse Aldridge
źródło
Jakiej wersji Pythona używasz? Przed wersją 2.2 można było używać im_class, ale zostało to zmienione, aby pokazać typ powiązanego obiektu własnego.
Kathy Van Stone
1
Dobrze wiedzieć. Ale używam 2.6.
Jesse Aldridge

Odpowiedzi:

71
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
źródło
1
Dzięki, zajęłoby mi trochę czasu,
David
Uwaga, nie wszystkie klasy implementują __dict__! Czasami __slots__jest używany. Prawdopodobnie lepiej jest użyć getattrdo sprawdzenia, czy metoda znajduje się w klasie.
Codie CodeMonkey,
16
Dla Python 3 , proszę odnieść się do tej odpowiedzi .
Yoel
27
Dostaję:'function' object has no attribute 'im_class'
Zitrax
5
W Pythonie 2.7 to nie działa. Ten sam błąd dotyczący braku „im_class”.
RedX
8

Dzięki Sr2222 za wskazanie, że nie rozumiem ...

Oto poprawione podejście, które jest podobne do podejścia Alexa, ale nie wymaga niczego importowania. Nie sądzę jednak, że jest to poprawa, chyba że istnieje ogromna hierarchia klas dziedziczonych, ponieważ to podejście zatrzymuje się, gdy tylko zostanie znaleziona klasa definiująca, zamiast zwracać całe dziedziczenie getmro. Jak powiedziałem, jest to bardzo mało prawdopodobny scenariusz.

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

I przykład:

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

Rozwiązanie Alex zwraca te same wyniki. O ile można zastosować podejście Alexa, użyłbym go zamiast tego.

estani
źródło
Cls().meth.__self__po prostu podaje przykład, Clsktóry jest powiązany z tym konkretnym wystąpieniem meth. To jest analogiczne do Cls().meth.im_class. Jeśli tak class SCls(Cls), SCls().meth.__self__otrzymasz SClsinstancję, a nie Clsinstancję. To, czego chce OP, to dostać Cls, co wydaje się dostępne tylko chodząc po MRO, tak jak robi to @Alex Martelli.
Silas Ray
@ sr2222 Masz rację. Zmodyfikowałem odpowiedź, ponieważ już zacząłem, chociaż myślę, że rozwiązanie Alex jest bardziej kompaktowe.
estani
1
To dobre rozwiązanie, jeśli chcesz uniknąć importu, ale ponieważ w zasadzie tylko ponownie wdrażasz MRO, nie ma gwarancji, że będzie działać wiecznie. MRO prawdopodobnie pozostanie taki sam, ale został już raz zmieniony w przeszłości Pythona, a jeśli zostanie zmieniony ponownie, ten kod spowoduje subtelne, wszechobecne błędy.
Silas Ray
W programowaniu często zdarzają się „bardzo nieprawdopodobne scenariusze”. Rzadko powodując katastrofy. Ogólnie rzecz biorąc, wzorzec myślenia „XY.Z% to się nigdy nie wydarzy” jest wyjątkowo kiepskim narzędziem do myślenia podczas kodowania. Nie używaj tego. Napisz w 100% poprawny kod.
ulidtko
@ulidtko Myślę, że źle odczytałeś wyjaśnienie. Nie chodzi o poprawność, ale o szybkość. Nie ma „idealnego” rozwiązania, które pasowałoby do wszystkich przypadków, w przeciwnym razie będzie np. Tylko jeden algorytm sortowania. Zaproponowane tutaj rozwiązanie powinno być szybsze w „rzadkim” przypadku. Ponieważ prędkość osiąga 99% procent wszystkich przypadków, biorąc pod uwagę czytelność, to rozwiązanie może być lepszym rozwiązaniem „tylko” w tym rzadkim przypadku. Kod, jeśli go nie czytałeś, jest w 100% poprawny, jeśli tego się obawiałeś.
estani
6

Nie wiem, dlaczego nikt nigdy o tym nie wspominał ani dlaczego najlepsza odpowiedź ma 50 głosów pozytywnych, kiedy jest wolna jak diabli, ale możesz też wykonać następujące czynności:

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

Uważam, że w przypadku Pythona 3 to się zmieniło i musisz się temu przyjrzeć .__qualname__.

Nick Chapman
źródło
2
Hmm. Nie widzę klasy definiującej, kiedy robię to w Pythonie 2.7 - Otrzymuję klasę, na której metoda została wywołana, a nie tę, w której jest zdefiniowana ...
F1Rumors
5

W Pythonie 3, jeśli potrzebujesz rzeczywistego obiektu klasy, możesz zrobić:

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

Jeśli funkcja mogłaby należeć do zagnieżdżonej klasy, należałoby wykonać iterację w następujący sposób:

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
źródło
1

Zacząłem robić coś podobnego, zasadniczo chodziło o sprawdzenie, czy metoda w klasie bazowej została zaimplementowana, czy nie w podklasie. Okazało się, że tak jak pierwotnie to zrobiłem, nie mogłem wykryć, kiedy klasa pośrednia faktycznie implementowała tę metodę.

Moje obejście było całkiem proste; ustawienie atrybutu metody i późniejsze sprawdzenie jego obecności. Oto uproszczenie całej rzeczy:

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

AKTUALIZACJA: Właściwie wywołaj method()from run_method()(czy to nie jest duch?) I przekaż wszystkie niezmodyfikowane argumenty do metody.

PS: Ta odpowiedź nie odpowiada bezpośrednio na pytanie. IMHO Istnieją dwa powody, dla których chciałoby się wiedzieć, która klasa zdefiniowała metodę; pierwszym jest wskazanie palcem klasy w kodzie debugowania (na przykład w obsłudze wyjątków), a drugim jest określenie, czy metoda została ponownie zaimplementowana (gdzie metoda jest kodem pośrednim przeznaczonym do zaimplementowania przez programistę). Ta odpowiedź rozwiązuje ten drugi przypadek w inny sposób.

Thomas Guyot-Sionnest
źródło
1

Python 3

Rozwiązałem to w bardzo prosty sposób:

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

To daje

'FooClass.foo_method'

Podziel na kropkę, aby osobno pobrać klasę i nazwę funkcji

firelynx
źródło
Wow, co za oszczędność!
user3712978