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)

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

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.


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


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

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.


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


Wow, co za oszczędność!
user3712978
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.