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

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

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

Estani
источник
Cls().meth.__self__просто дает вам экземпляр, Clsкоторый привязан к этому конкретному экземпляру meth. Это аналог Cls().meth.im_class. Если у вас есть class SCls(Cls), SCls().meth.__self__вы получите SClsэкземпляр, а не Clsэкземпляр. OP хочет получить Cls, что, по-видимому, доступно только при прохождении MRO, как это делает @Alex Martelli.
Сайлас Рэй
@ sr2222 Вы правы. Я изменил ответ, поскольку я уже начал, хотя я думаю, что решение Alex более компактно.
estani
1
Это хорошее решение, если вам нужно избежать импорта, но поскольку вы в основном просто повторно внедряете MRO, это не гарантирует, что он будет работать вечно. MRO, вероятно, останется прежним, но он уже был изменен однажды в прошлом Python, и если он будет изменен снова, этот код приведет к появлению тонких, широко распространенных ошибок.
Сайлас Рэй
Снова и снова в программировании случаются «очень маловероятные сценарии». Редко, вызывая катастрофы. В общем, шаблон мышления «XY.Z%, этого никогда не случится» - крайне плохой инструмент мышления при написании кода. Не используйте это. Напишите 100% правильный код.
Улидтко
@ulidtko Я думаю, вы неправильно прочитали объяснение. Дело не в правильности, а в скорости. Не существует «идеального» решения, подходящего для всех случаев, иначе, например, будет только один алгоритм сортировки. Предлагаемое здесь решение должно быть быстрее в «редком» случае. Так как скорость достигается в 99% случаев после удобочитаемости, это решение может быть лучшим решением «только» в этом редком случае. Код, если вы его не читали, на 100% правильный, если вы этого и опасались.
Estani
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

UPDATE: На самом деле звонить method()из run_method()(не то, что дух?), Они должны передать все аргументы немодифицированных к методу.

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

Томас Гайо-Сионнест
источник
1

Python 3

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

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

Это дает

'FooClass.foo_method'

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

Firelynx
источник
Вау, какая экономия!
user3712978