Есть ли способ ссылаться на базовый __class__ объекта из области видимости класса?

#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),
}