Отримати клас, який визначено методом


89

Як я можу отримати клас, який визначив метод у Python?

Я хочу, щоб наступний приклад надрукував " __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)

Яку версію Python ви використовуєте? До 2.2 ви могли використовувати im_class, але це було змінено, щоб показати тип пов'язаного self-об'єкта.
Кеті Ван Стоун

1
Добре знати. Але я використовую 2.6.
Джессі Олдрідж

Відповіді:


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
Дякую, мені зайняв би це час, щоб зрозуміти це самостійно
Девід

Обережно, не всі класи реалізують __dict__! Іноді __slots__використовується. Ймовірно, краще використовувати, getattrщоб перевірити, чи метод у класі.
Codie CodeMonkey

16
Для Python 3, будь ласка, зверніться до цієї відповіді .
Йоел

27
Я отримую:'function' object has no attribute 'im_class'
Zitrax

5
У Python 2.7 це не працює. Та сама помилка про відсутність "im_class".
RedX

8

Дякуємо Sr2222, що вказав, що я пропустив суть ...

Ось виправлений підхід, який схожий на підхід Алекса, але не вимагає нічого імпортувати. Я не думаю, що це поліпшення, якщо не існує величезної ієрархії успадкованих класів, оскільки цей підхід зупиняється, як тільки визначальний клас буде знайдений, замість того, щоб повертати ціле успадкування як getmroце робиться. Як вже було сказано, це дуже малоймовірний сценарій.

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

І приклад:

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

Рішення Алекса повертає ті самі результати. Поки можна використовувати підхід Алекса, я б використовував його замість цього.


Cls().meth.__self__просто надає вам примірник, Clsякий прив'язаний до цього конкретного примірника meth. Це аналогічно Cls().meth.im_class. Якщо у вас є class SCls(Cls), SCls().meth.__self__ви отримаєте SClsекземпляр, а не Clsекземпляр. Те, що хоче ОП, - це отримати Cls, що, здається, доступно лише пройшовши MRO, як це робить @Alex Martelli
Сайлас Рей

@ sr2222 Ви маєте рацію. Я змінив відповідь, оскільки вже почав, хоча, на мою думку, рішення Alex є більш компактним.
estani

1
Це хороше рішення, якщо вам потрібно уникнути імпорту, але оскільки ви в основному просто повторно впроваджуєте MRO, це не гарантовано буде працювати вічно. MRO, ймовірно, залишиться незмінним, але його вже було змінено колись у минулому Python, і якщо його буде змінено ще раз, цей код призведе до тонких, поширених помилок.
Сайлас Рей

Неодноразово в програмуванні трапляються "дуже малоймовірні сценарії". Рідко, спричиняючи катастрофи. Загалом, шаблон мислення "XY.Z% цього ніколи не станеться" є надзвичайно кепським інструментом мислення при кодуванні. Не використовуйте його. Напишіть 100% правильний код.
ulidtko

@ulidtko Я думаю, ти неправильно прочитав пояснення. Справа не в коректності, а в швидкості. Не існує "ідеального" рішення, яке відповідає всім випадкам, інакше, наприклад, буде лише один алгоритм сортування. Запропоноване тут рішення має бути швидшим у "рідкісному" випадку. Оскільки швидкість припадає на 99% відсотків усіх випадків після читабельності, це рішення може бути кращим рішенням лише в тому рідкісному випадку. Код, якщо ви його не читали, є на 100% правильним, якщо цього було те, чого ви боялися.
естані

6

Я не знаю, чому ніхто ніколи цього не піднімав, або чому відповідь угорі має 50 голосів проти, коли це повільно, як біс, але ви також можете зробити наступне:

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

Що стосується python 3, я вважаю, що це змінилося, і вам потрібно буде вивчити .__qualname__.


2
Хм Я не бачу визначального класу, коли я це роблю в python 2.7 - я отримую клас, до якого був викликаний метод, а не той, де він визначений ...
F1Rumors

5

У Python 3, якщо вам потрібен фактичний об’єкт класу, ви можете зробити:

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

Якщо функція могла належати до вкладеного класу, вам потрібно було б виконати ітерацію наступним чином:

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

Я почав робити щось подібне, в основному ідея полягала в перевірці кожного разу, коли метод у базовому класі був реалізований чи ні в підкласі. Виявившись так, як я це робив спочатку, я не міг виявити, коли проміжний клас насправді реалізовував метод.

Моє обхідне рішення було насправді досить простим; встановлення атрибута методу та перевірка його присутності пізніше. Ось спрощення всього:

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

ОНОВЛЕННЯ: Насправді виклик method()від run_method()(це не дух?) І нехай він передає всі аргументи, не змінені, методу.

PS: Ця відповідь не відповідає безпосередньо на питання. IMHO є дві причини, чому хотілося б знати, який клас визначив метод; перший - вказати пальцем на клас у коді налагодження (наприклад, при обробці винятків), а другий - визначити, чи метод був повторно реалізований (де метод - заглушка, призначена для реалізації програмістом). Ця відповідь вирішує цю другу справу по-іншому.


1

Python 3

Вирішив це дуже простим способом:

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

Це дає

'FooClass.foo_method'

Розділіть на крапку, щоб отримати клас та ім'я функції окремо


Ого, який порятунок!
user3712978
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.