Имя класса, содержащего код метода

Я пытаюсь найти имя класса, содержащего код метода.

В приведенном ниже примере я использую self.__class__.__name__, но, конечно, это возвращает имя класса, для которого self является экземпляром, а не классом, который содержит код метода test(). b.test() будет печатать 'B', пока я хотел бы получить 'A'.

Я просмотрел документацию модуля inspect, но не нашел ничего полезного.

class A:
  def __init__(self):
    pass
  def test(self):
    print self.__class__.__name__

class B(A):
  def __init__(self):
    A.__init__(self)



a = A()
b = B()

a.test()
b.test()

Ответ 1

В Python 3.x вы можете просто использовать __class__.__name__. Имя __class__ мягко магия, а не то же, что и атрибут __class__ self.

В Python 2.x нет хорошего способа получить эту информацию. Вы можете использовать проверку стека, чтобы получить объект кода, а затем пройти иерархию классов, ища правильный метод, но он медленный и утомительный и, вероятно, сломается, когда вы этого не захотите. Вы также можете использовать метакласс или декоратор класса, чтобы каким-то образом обработать класс, но оба они довольно навязчивые. И вы можете сделать что-то действительно уродливое, например, получить доступ к self.__nonexistant_attribute, поймать AttributeError и извлечь имя класса из искаженного имени. Ни один из этих подходов не стоит того, чтобы просто не набирать имя дважды; по крайней мере забыв обновить имя, можно сделать немного более очевидным, сделав что-то вроде:

class C:
    ...
    def report_name(self):
        print C.__name__

Ответ 2

inspect.getmro дает вам кортеж классов, из которых метод может исходить, по порядку. Как только вы найдете один из них, у которого есть имя метода в dict, вы закончили:

for c in inspect.getmro(self.__class__):
    if 'test' in vars(c): break
return c.__name__

Ответ 3

Используйте __dict__ самого объекта класса:

class A(object):
    def foo(self):
        pass

class B(A):
    pass

def find_decl_class(cls, method):
    if method in cls.__dict__:
        return cls
    for b in cls.__bases__:
        decl = find_decl_class(b, method)
        if decl:
            return decl

print 'foo' in A.__dict__
print 'foo' in B.__dict__
print find_decl_class(B, 'foo').__name__

Будет напечатано True, False, A

Ответ 4

Вы можете использовать (злоупотреблять?) личное имя mangling для достижения этого эффекта. Если вы посмотрите атрибут self, начинающийся с __ внутри метода, python изменяет имя от __attribute до _classThisMethodWasDefinedIn__attribute.

Просто как-то запишите имя класса, которое вы хотите в искаженной форме, где метод может его увидеть. В качестве примера мы можем определить метод __new__ для базового класса, который делает это:

def mangle(cls, attrname):
    if not attrname.startswith('__'):
        raise ValueError('attrname must start with __')
    return '_%s%s' % (cls.__name__, attrname)

class A(object):

    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        for c in cls.mro():
            setattr(obj, mangle(c, '__defn_classname'), c.__name__)
        return obj

    def __init__(self):
        pass

    def test(self):
        print self.__defn_classname

class B(A):

    def __init__(self):
        A.__init__(self)



a = A()
b = B()

a.test()
b.test()

который печатает:

A
A

Ответ 5

Вы можете сделать

>>> class A(object):
...   def __init__(self):
...     pass
...   def test(self):
...     for b in self.__class__.__bases__:
...       if hasattr(b, 'test'):
...         return b.__name__
...     return self.__class__.__name__
... 
>>> class B(A):
...   def __init__(self):
...     A.__init__(self)
...
>>> B().test()
'A'
>>> A().test()
'A'
>>> 

Имейте в виду, что вы можете упростить его, используя __class__.__base__, но если вы используете множественное наследование, эта версия будет работать лучше.

Он просто проверяет сначала на своих базовых классах для test. Это не самый красивый, но он работает.