#python #python-3.x
#python #python-3.x
Вопрос:
Возможно ли получить родительский тип пространства имен или инкапсулирующий тип класса?
class base:
class sub:
def __init__(self):
# self is "__main__.extra.sub"
# want to create object of type "__main__.extra" from this
pass
class extra(base):
class sub(base.sub):
pass
o = extra.sub()
Проблема в base.sub.__init__
получении extra
из extra.sub
.
Единственные решения, которые я могу придумать на данный момент, включают в себя то, что все подклассы base
предоставляют некоторую ссылку на их тип инкапсулирующего класса или превращают тип self в base.sub.__init__
в строку и преобразуют его в строку нового типа. Оба немного уродливы.
Очевидно, что можно пойти другим путем, type(self()).sub
чтобы дать вам extra.sub
изнутри base.sub.__init__
для extra
объекта типа, но как мне это сделать ..
вместо .sub
? 🙂
Комментарии:
1. Классы Python не могут видеть входящие классы. Помещение
sub
в другой класс ничего не дает.2. Крайне неясно, почему вы думаете, что
extra
имеет какое-либо отношение кbase.sub
. Пожалуйста, лучше объясните свои предположения и опубликуйте фактический MCVE.3. Прошу прощения 🙂 Я думал, что это было очень ясно. Во время выполнения, учитывая эти классы, полиморфизм означает, что
self
inbase.sub.__init__
МОЖЕТ иметь типbase.sub
илиextra.sub
. Приведенный выше код на самом деле доступен для выполнения, и при выполненииself
будет введен типextra.sub
вbase.sub
конструкторе. Получение типаextra
изextra.sub
— вот к чему это сводится.4. Я думаю, что предположить, что
extra
не имеет ничего общего сbase.sub
, слишком сильно,extra
связано сbase
по наследованию иbase
связано сbase.sub
по пространству имен. Нужно было бы разобраться в определении «связанного» и начать обсуждение теории отношений, чтобы продвинуться дальше, но это мне не поможет и не заинтересует. Рассматриваемая проблема заключается в следовании отношениям пространства имен отextra.sub
доextra
, это явно семантически возможно и часто выполняется даже в различных контекстах. При записиfrom .. import x
мы, например, возвращаемся к пространству имен модуля.5. Я имею в виду практические отношения в языке, а не какую-то абстрактную концепцию. Тела вложенных классов не знают ни о какой охватывающей области, кроме глобальной. Сам класс — это просто объект. При желании на него можно иметь несколько ссылок. Я не согласен с тем, что ваш пример выполняется. Я просто отрабатываю другой набор предположений, поэтому я не понимаю, почему вы думаете, что показанный вами код должен работать. Я могу объяснить гораздо более подробно, почему python предназначен для этого, но не для этого. И, возможно, найти обходной путь.
Ответ №1:
Реальный ответ заключается в том, что общего способа сделать это не существует. Классы Python — это обычные объекты, но они создаются немного по-другому. Класс не существует до тех пор, пока не будет выполнено все его тело. Как только класс создан, он может быть привязан ко многим различным именам. Единственная ссылка, которая у него есть на то, где он был создан, — это атрибуты __module__
и __qualname__
, но оба они изменяемы.
На практике ваш пример можно написать следующим образом:
class Sub:
def __init__(self):
pass
class Base:
Sub = Sub
Sub.__qualname__ = 'Base.Sub'
class Sub(Sub):
pass
class Extra(Base):
Sub = Sub
Sub.__qualname__ = 'Extra.Sub'
del Sub # Unlink from global namespace
За исключением заглавных букв, это ведет себя точно как ваш исходный пример. Надеюсь, это прояснит, какой код к чему имеет доступ, и покажет, что наиболее надежный способ определить охватывающую область класса — это явно назначить ее где-нибудь. Вы можете сделать это любым количеством способов. Тривиальный способ — просто назначить его. Возвращаемся к исходной записи:
class Base:
class Sub:
def __init__(self):
print(self.enclosing)
Base.Sub.enclosing = Base
class Extra(Base):
class Sub(Base.Sub):
pass
Extra.Sub.enclosing = Extra
Обратите внимание, что, поскольку Base
не существует, когда выполняется его тело, назначение должно произойти после создания обоих классов. Вы можете обойти это, используя метакласс или декоратор. Это позволит вам возиться с пространством имен до того, как объекту класса будет присвоено имя, что сделает изменение более прозрачным.
class NestedMeta(type):
def __init__(cls, name, bases, namespace):
for name, obj in namespace.items():
if isinstance(obj, type):
obj.enclosing = cls
class Base(metaclass=NestedMeta):
class Sub:
def __init__(self):
print(self.enclosing)
class Extra(Base):
class Sub(Base.Sub):
pass
Но это снова несколько ненадежно, потому что не все метаклассы являются экземпляром type
, что возвращает нас к первому утверждению в этом ответе.
Комментарии:
1. «Нет ответа» и «невозможно» — это совершенно разумный и законный ответ. Я принимаю этот более интересный ответ в отсутствие кого-либо, кто пока не согласен с этим, и потому, что я тоже не мог считать это возможным разумным способом. Как вы указываете здесь, существуют другие пути с метаклассами и фабриками, по которым можно ходить; альтернативные стратегии проектирования могут в конечном итоге оказаться правильным способом найти себя на пути, который не вызывает вопроса, но иногда нежелательно или нецелесообразно тратить время и вносить более обширные изменения в базу кода для решения крошечной проблемы.
2. @Michael. Я согласен, что этот ответ более подробный, но вы должны признать, что другой гораздо более практичен в том смысле, что он правильно охватывает 90% случаев и не является огромной проблемой для реализации.
3. Да, другой ответ является допустимым ответом, некоторые могут даже сказать, что на самом деле он более действителен, но это также немного улучшенная версия взлома, которую я предложил в комментариях к вопросу, и я не могу заставить себя иметь что-либо подобное любой версии в любом журнале фиксации с моим именем рядом с ним, и, возможно, это ответ, который мы не должны признавать, также является одним 🙂 Я выбрал этот ответ для принятия не потому, что он был более глубоким, а потому, что (оставляя в стороне ужасы) «нет общего способа сделать это», как мне кажется, является более подходящим ответом, который мы должны стремиться принять как настоящую правду.
4. @Michael. Я в основном согласен с вами в этом, что довольно неизбежно, учитывая, что сейчас мы разделяем одну и ту же информацию. Я бы предположил, что нет ничего плохого в том, чтобы явно сказать «Я хочу, чтобы это был мой охватывающий класс», будь то в метаклассе или с помощью ручного назначения.
Ответ №2:
Во многих случаях вы можете использовать атрибуты __qualname__
и __module__
, чтобы получить имя окружающего класса:
import sys
cls = type(o)
getattr(sys.modules[cls.__module__], '.'.join(cls.__qualname__.split('.')[:-1]))
Это очень буквальный ответ на ваш вопрос. Это просто показывает один из способов получения класса во включающей области без устранения возможных недостатков дизайна, которые приводят к тому, что это необходимо в первую очередь, или любой из многих возможных угловых случаев, которые это не будет охватывать.