#python #python-3.x #oop #superclass
#python #python-3.x #ооп #суперкласс
Вопрос:
У меня есть серия классов all, наследуемых от одного из нескольких возможных родителей, которые все разделяют базовый класс. У каждого класса есть Dict области видимости класса [str, object] parameters
, который основан на базовом классе parameters
from copy import deepcopy
class BaseClass:
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(Baseclass):
parameters = {
**deepcopy(Baseclass.parameters),
'example_param2': ParamObject(name='example_param2', editable=False),
}
class ChildClass(MiddleClass):
parameters = {
**deepcopy(MiddleClass.parameters),
'example_param3': ParamObject(name='example_param3', editable=True),
}
Эта реализация выполняет свою работу, но я нахожу **deepcopy(Baseclass.parameters),
строку неудовлетворительной. Эти дочерние классы со временем будут редактироваться кем-то, имеющим лишь базовое представление о кодировании, поэтому я хочу сделать код максимально простым и доступным для вырезания и вставки.
Есть ли что-нибудь, что я могу вызвать в области видимости класса, чтобы получить эквивалент super().__class__
? Я хочу, чтобы пользователь мог изменять базовый класс, скажем, MiddleClass
на MiddleClass2
, не забывая менять базовый класс в нескольких местах.
Комментарии:
1. Обратите внимание, что это
super().__class__
тоже не будет делать то, что вы хотите, даже внутри метода экземпляра — он просто выдаетsuper
.2. Вероятно, это случай
__init_subclass__
, хотя мне не ясно, как лучше всего это структурировать.3. Обязательно ли параметры должны быть в dict? Если бы они были прямыми атрибутами класса, вы получили бы желаемое поведение наследования бесплатно. Если они должны быть отличимы от других атрибутов класса, может быть достаточно соглашения об именовании (например,
*_param*
в вашем примере).4. Для этого я бы использовал метаклассы. Вы могли бы пройти MRO и объединить все эти параметры.
5. @alkasm
__init_subclass__
существует, чтобы избежать необходимости использовать метакласс для этого типа варианта использования.
Ответ №1:
Вы можете использовать __init_subclass__
для добавления параметров из родительских классов непосредственно к явно определенному parameters
атрибуту за счет изменения порядка вставки (если это имеет значение).
class ParameterBase:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for pc in cls.__bases__:
d = getattr(pc, 'parameters', {})
cls.parameters.update(d)
class BaseClass(ParameterBase):
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(BaseClass):
parameters = {
'example_param2': ParamObject(name='example_param2', editable=False),
}
class ChildClass(MiddleClass):
parameters = {
'example_param3': ParamObject(name='example_param3', editable=True),
}
Комментарии:
1. В этом случае порядок, хотя и не имеет большого значения, может вызвать некоторые неинтуитивные побочные эффекты в другом месте. Однако я упомяну, что я не знал о существовании init_subclass, и я уже думаю о других местах, где это может мне помочь, так что спасибо за ответ!
Ответ №2:
Вы могли бы сделать это с помощью метакласса. Проверьте все классы в дереве наследования, чтобы увидеть, есть ли у них поле parameter
s. Если они это сделают, затем объедините их вместе и установите свойство для экземпляра класса.
class ParamMeta(type):
def __init__(cls, name, bases, dct):
class_types = [cls] list(bases)
parameters = {}
for class_type in class_types:
if hasattr(class_type, "parameters"):
parameters.update(class_type.parameters)
cls.parameters = parameters
super().__init__(name, bases, dct)
Пример:
class Foo(metaclass=ParamMeta):
parameters = {"a": "b"}
class Bar(Foo):
pass
class Fizz(Bar):
parameters = {"c": "d"}
print(Foo.parameters)
print(Bar.parameters)
print(Fizz.parameters)
Выводит:
{'a': 'b'}
{'a': 'b'}
{'c': 'd', 'a': 'b'}
Ответ №3:
Если ваш класс наследуется только от одного класса, вы можете использовать метакласс:
class Meta(type):
def __new__(cls, name, parents, namespace):
namespace['parameters'] = {
**deepcopy(parents[0].parameters),
**namespace['parameters']
}
return super().__new__(cls, name, parents, namespace)
class MiddleClass(Baseclass, metaclass=Meta):
parameters = {
'example_param2': ParamObject(name='example_param2', editable=False),
}
Это, конечно, упрощенная версия, которая, например, не обрабатывает несколько родительских классов.
Ответ №4:
Вы можете использовать метаклассы, такие как другие предложенные ответы, но я не уверен, насколько это было бы проще для понимания начинающим программистом.
Вы можете получить доступ к классам, унаследованным от класса, вызвав __bases__
класс. К сожалению, на python вы не можете ссылаться на класс, который вы создаете, в теле класса, что означает, что вы не можете сделать:
class A:
bases = A.__bases__ # Invalid code, A is not defined yet
Если вы хотите сделать это, сохраняя статические параметры, вы можете сделать это после определения класса, и у него будет точное поведение, которое вы ищете, хотя и немного уродливое:
class BaseClass:
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(Baseclass):
pass
MidleClass.parameters = {
**deepcopy(MidleClass.__bases__[0].parameters),
'example_param2': ParamObject(name='example_param2', editable=False),
}
Теперь, чтобы немного улучшить ситуацию, если ваш parameters
действительно всегда является ‘Dict [str, object]’, вам не нужно выполнять глубокую копию, поскольку использование **
действует функционально. Вы также можете обрабатывать несколько родительских классов, используя __bases__
значение, чтобы гарантировать, что каждое значение фиксируется. Итак, если бы я должен был это сделать, я бы сделал что-то вроде этого:
def base_parameters(cls):
parameters = {}
for base in cls.__bases__:
parameters.update(base.parameters)
return parameters
class BaseClass:
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(BaseClass):
pass
MiddleClass.parameters = {
**base_parameters(MiddleClass),
'example_param2': ParamObject(name='example_param2', editable=False),
}
class ChildClass(MiddleClass):
pass
ChildClass.parameters = {
**base_parameters(ChildClass),
'example_param3': ParamObject(name='example_param3', editable=True),
}