Доступ к __классу__ ребенка из родительского метода в Python

#python #class #inheritance

Вопрос:

Когда я запускаю приведенный ниже код, в конце я получаю ссылку на родительский класс A , в то время как я надеялся сослаться на дочерний B .

 In [1]: class A:
    ...:     def f():
    ...:         print(__class__)
    ...: 

In [2]: class B(A): pass

In [3]: B.f()
<class '__main__.A'>
 

Я знаю, что могу сделать что-то вроде

 In [4]: class A:
    ...:     def f(self):
    ...:         print(self.__class__)
    ...: 

In [5]: class B(A): pass

In [6]: B().f()
<class '__main__.B'>
 

но мне интересно, есть ли какой-нибудь способ убрать лишние скобки.

Комментарии:

1. Почему вы ожидали B.f() этого? Это просто функция, определенная в блоке класса A.

2. @wim: Забавно, что это не просто функция; тот факт, что неквалифицированный __class__ вообще работает, заключается в том, что определение в A class блоке изменяется f по сравнению с тем, что было бы, если бы оно не было определено в A class блоке (оно неявно оборачивает его в поддельную область закрытия, которая обеспечивает __class__ as A ). __class__ является ли магия, которую ОП не понимает; Я подозреваю, что они думали, что это магия времени выполнения, когда на самом деле это (своего рода) магия времени определения (это скорее магия времени определения класса, чем магия времени определения функции, но это и то, и другое).

3. type(B.f) это функция, type(B().f) это метод. В заголовке вопроса задается вопрос о «родительском методе», но в тексте вопроса не используется метод. Функция без аргументов, определенных в блоке класса, и не украшенная @staticmethod, — это довольно странная вещь, которую нужно делать в первую очередь, поэтому я в некотором роде не понимаю, что OP пытается здесь сделать в любом случае. Вопрос XY?

4. @wim: Конечно, но люди, новички в Python (и даже люди, которые не так уж новички в нем), часто путают терминологию. Черт, в Питон 2 дней, B.f в действительности метод (несвязанный метод, но немного отличается от обычной функции), так что старые справочные материалы может быть оправдано смешение их немного в этом конкретном случае, тем более что термин «метод» может обоснованно охарактеризовать функции, определенной внутри класса в аннотации, даже если это на самом деле не привязаны к конкретному экземпляру еще.

5. @wim: Проблема XY вполне возможна. Я предполагаю, что они отказались от каких-либо аргументов, потому что хотели использовать его только в этом сценарии без экземпляров, и предоставление ему каких-либо аргументов помешало бы им даже попытаться его вызвать. Они, вероятно, не знали об @staticmethod этом (если бы они знали, то знали бы об @classmethod этом, и им не нужно было бы задавать вопрос).

Ответ №1:

Если вы хотите также иметь возможность вызывать функцию в классе, а не только в экземпляре, и знать, в каком классе она была вызвана (а для экземпляра-класс экземпляра, в котором она была вызвана), сделайте метод a classmethod , например:

 class A:
    @classmethod
    def f(cls):
        print(cls) 

class B(A):
    pass
 

classmethod Декоратор создает дескриптор, который автоматически предоставит объект класса, для которого он был вызван, независимо от того, был ли он вызван для самого класса или экземпляра класса, и предоставляет объект класса в качестве первого аргумента, а не экземпляра. Он почти исключительно используется для определения альтернативных конструкторов (с целью принятия некоторого формата аргументов , не принятого __init__ , преобразования из этого формата в __init__ приемлемый, а затем завершения return cls(converted args here) ), но если вы действительно настаиваете на:

  1. Используя для этого классы, и
  2. Не создавать экземпляры классов по какой-либо причине

он может обрабатывать оба ваших варианта использования ( B.f() и B().f() ); classmethod удаляет информацию об экземпляре при вызове, поэтому последний ведет себя идентично первому (оба они передаются B как значение cls in f ).

print(__class__) в одиночку не работают, потому что __class__ это намеренно привязали к классу функция была определена, как часть оборудования для производства без аргументов super() звонки работы (он использует некоторые довольно ужасные хаки, чтобы super() вести себя, как это было на самом деле называется первый позиционный аргумент, переданный функции, как правило self , плюс __class__ переменная незаконно ввезенных на закрытие возможностей при классе функция была определена в отделке определяется); делая __class__ пересмотреть свою очередь, основывается на необходимости подкласс бы превратить все использование super() в бесконечной рекурсии.