Получить класс, который определил метод

Как я могу получить класс, который определил метод в 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)

person Jesse Aldridge    schedule 07.06.2009    source источник
comment
Какую версию Python вы используете? До 2.2 вы могли использовать im_class, но это было изменено, чтобы показать тип привязанного объекта self.   -  person Kathy Van Stone    schedule 07.06.2009
comment
Хорошо знать. Но я использую 2.6.   -  person Jesse Aldridge    schedule 07.06.2009


Ответы (6)


Спасибо 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

Решение Alex возвращает те же результаты. Пока можно использовать подход Alex, я бы использовал его вместо этого.

person estani    schedule 29.11.2012
comment
Cls().meth.__self__ просто дает вам экземпляр Cls, связанный с этим конкретным экземпляром meth. Аналог Cls().meth.im_class. Если у вас есть class SCls(Cls), SCls().meth.__self__ даст вам экземпляр SCls, а не экземпляр Cls. OP хочет получить Cls, который, похоже, доступен только при прохождении MRO, как это делает @Alex Martelli. - person Silas Ray; 20.12.2012
comment
@ sr2222 Вы правы. Я изменил ответ, поскольку я уже начал, хотя я думаю, что решение Alex более компактно. - person estani; 30.12.2012
comment
Это хорошее решение, если вам нужно избежать импорта, но поскольку вы в основном просто повторно внедряете MRO, это не гарантирует, что оно будет работать вечно. MRO, вероятно, останется прежним, но он уже был изменен однажды в прошлом Python, и если он будет изменен снова, этот код приведет к появлению тонких, широко распространенных ошибок. - person Silas Ray; 02.01.2013
comment
Снова и снова в программировании случаются очень маловероятные сценарии. Редко, вызывая катастрофы. В общем, шаблон мышления XY.Z%, этого никогда не случится является крайне паршивым инструментом мышления при написании кода. Не используйте это. Напишите 100% правильный код. - person ulidtko; 07.05.2020
comment
@ulidtko Я думаю, вы неправильно прочитали объяснение. Дело не в правильности, а в скорости. Не существует идеального решения, подходящего для всех случаев, иначе, например, будет только один алгоритм сортировки. Предлагаемое здесь решение должно быть быстрее в редком случае. Поскольку скорость достигается в 99% случаев после удобочитаемости, это решение может быть лучшим решением только в этом редком случае. Код, если вы его не читали, на 100% правильный, если вы этого и опасались. - person estani; 10.05.2020
comment
Предупреждение, im_class является атрибутом только Python 2, поэтому он не будет работать с несвязанными методами в Python 3 (которые являются просто обычными функциями). - person Anakhand; 13.12.2020

Я не знаю, почему никто никогда не поднимал этот вопрос или почему за лучший ответ 50 голосов, когда он чертовски медленный, но вы также можете сделать следующее:

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

Я считаю, что для python 3 это изменилось, и вам нужно будет изучить .__qualname__.

person Nick Chapman    schedule 02.10.2017
comment
Хм. Я не вижу определяющий класс, когда делаю это в python 2.7 - я получаю класс, для которого был вызван метод, а не тот, где он определен ... - person F1Rumors; 27.08.2018

В 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
person Matthew D. Scholefield    schedule 19.04.2019

Я начал делать что-то похожее, в основном идея заключалась в том, чтобы проверять, был ли реализован метод в базовом классе или нет в подклассе. Оказалось, как я это делал изначально, я не мог определить, когда промежуточный класс действительно реализовывал метод.

На самом деле мой обходной путь был довольно простым; установка атрибута метода и проверка его наличия позже. Вот упрощение всего:

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() (разве это не дух?) И передайте все аргументы методу без изменений.

P.S .: Этот ответ не дает прямого ответа на вопрос. ИМХО, есть две причины, по которым нужно знать, какой класс определяет метод; первый - указать пальцем на класс в отладочном коде (например, при обработке исключений), а второй - определить, был ли метод повторно реализован (где метод - это заглушка, предназначенная для реализации программистом). Этот ответ решает второй случай другим способом.

person Thomas Guyot-Sionnest    schedule 15.10.2014

Python 3

Решил очень просто:

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

Это дает

'FooClass.foo_method'

Разделите на точку, чтобы получить класс и имя функции отдельно

person firelynx    schedule 17.09.2020
comment
Вау, какая экономия! - person user3712978; 05.11.2020
comment
Это также можно упростить до bar.foo_method.__qualname__, чтобы получить 'FooClass.foo_method. Я не знаю, есть ли крайние случаи для этого подхода, но он действительно работает для рассматриваемого вопроса. - person FMc; 10.04.2021

person    schedule
comment
Спасибо, мне бы потребовалось время, чтобы разобраться в этом самостоятельно - person David; 12.01.2010
comment
Остерегайтесь, не все классы реализуют __dict__! Иногда используется __slots__. Вероятно, лучше использовать getattr, чтобы проверить, находится ли метод в классе. - person Codie CodeMonkey; 12.03.2013
comment
@DeepYellow: _ slots_, вероятно, не имеют отношения к методу, определенному в классе, поскольку они описывают хранение в памяти в экземплярах этого класса. Методы, непосредственно реализованные в классе, всегда должны находиться в его _ dict_. - person sdupton; 21.03.2013
comment
Для Python 3 см. этот ответ. - person Yoel; 21.09.2014
comment
Я получаю: 'function' object has no attribute 'im_class' - person Zitrax; 24.06.2015
comment
@Zitrax, он работает в Python 2 - для Python 3 перейдите по ссылке в комментарии Йоэля непосредственно перед вашим. - person Alex Martelli; 30.07.2015
comment
В Python 2.7 это не работает. Та же ошибка, связанная с отсутствием im_class. - person RedX; 23.05.2016
comment
Если meth - это classmethod, замените im_class на im_self. Работает в Python 2.7.14. @AlexMartelli, возможно, вы захотите добавить это к ответу. - person Dennis Golomazov; 20.10.2017
comment
для тех, кто использует только Python 3 - что-то не так с использованием meth.__qualname__? - person Marc; 26.10.2017
comment
Для справки в будущем: предлагается Отклоненное изменение: пожалуйста, не сокращайте метод как мет; Мет - это наркотик: Это изменение предназначено для обращения к автору сообщения и не имеет смысла как изменение. Это должно было быть написано как комментарий или ответ. - person Nimantha; 25.05.2020