Подтвердить что ты не робот

Доступ к атрибуту родительского класса из тела подкласса

У меня есть класс Klass с атрибутом class my_list. У меня есть подкласс этого SubKlass, в котором я хочу иметь атрибут class my_list, который является модифицированной версией того же атрибута из родительского класса:

class Klass():
    my_list = [1, 2, 3]


class SubKlass(Klass):
    my_list = Klass.my_list + [4, 5] # this works, but i must specify parent class explicitly
    #my_list = super().my_list + [4, 5] # SystemError: super(): __class__ cell not found
    #my_list = my_list + [4, 5] # NameError: name 'my_list' is not defined 


print(Klass.my_list)
print(SubKlass.my_list)

Итак, есть ли способ получить доступ к атрибуту родительского класса без указания его имени?

UPDATE:

В Python есть ошибка: http://bugs.python.org/issue11339. Будем надеяться, что в какой-то момент он будет .

4b9b3361

Ответ 1

Вы не можете.

Определение класса, работающее в Python, работает следующим образом.

  • Интерпретатор видит инструкцию class, за которой следует блок кода.

  • Создает новое пространство имен и выполняет этот код в пространстве имен.

  • Он вызывает конструкцию type с результирующим пространством имен, именем класса, базовыми классами и метаклассом (если применимо).

  • Он присваивает результат названию класса.

При запуске кода внутри определения класса вы не знаете, что такое базовые классы, поэтому вы не можете получить их атрибуты.

Что вы можете сделать, так это изменить класс сразу после его определения.


EDIT: здесь немного декоратора класса, который вы можете использовать для обновления атрибута. Идея состоит в том, что вы даете ей имя и функцию. Он просматривает все базовые классы вашего класса и получает их атрибуты с этим именем. Затем он вызывает функцию со списком значений, унаследованных от базового класса, и значением, определенным в подклассе. Результат этого вызова связан с именем.

Код может иметь смысл:

>>> def inherit_attribute(name, f):
...     def decorator(cls):
...             old_value = getattr(cls, name)
...             new_value = f([getattr(base, name) for base in cls.__bases__], old_value)
...             setattr(cls, name, new_value)
...             return cls
...     return decorator
... 
>>> def update_x(base_values, my_value):
...    return sum(base_values + [my_value], tuple())
... 
>>> class Foo: x = (1,)
... 
>>> @inherit_attribute('x', update_x)
... class Bar(Foo): x = (2,)
... 
>>> Bar.x
(1, 2)

Идея состоит в том, что вы определяете x как (2,) в Bar. Затем декоратор перейдет и просмотрит подклассы Bar, найдет все их x s и вызовет update_x с ними. Поэтому он будет называть

update_x([(1,)], (2,))

Он объединяет их, объединяя их, а затем снова связывает их с x. Это имеет смысл?

Ответ 2

Как ответил @katrielalex, my_list по умолчанию не находится в пространстве имен до того, как класс был создан. Однако, что вы могли бы сделать в Python 3, если хотите погрузиться в метаклассы, нужно добавить my_list в пространство имен вручную:

class Meta(type):
    def __prepare__(name, bases, **kwds):
        print("preparing {}".format(name), kwds)
        my_list = []
        for b in bases:
            if hasattr(b, 'my_list'):
                my_list.extend(b.my_list)
        return {'my_list': my_list}

class A(metaclass=Meta):
    my_list = ["a"]

class B(A):
    my_list.append(2)

print(A().my_list)
print(B().my_list)

Обратите внимание, что этот пример, вероятно, еще не обрабатывает структуры наследования в форме алмаза.

Новый атрибут __prepare__ определяется в PEP 3115.

Ответ 3

Вы можете использовать метакласс или декоратор класса. Здесь, как вы бы использовали метакласс в Python 2.x:

class Klass(object):
    my_list = [1, 2, 3]

class KlassMeta(type):

    def __new__(cls, name, bases, attrs):
        # grab last base class, which in this case is Klass
        attrs['my_list'] = bases[-1].my_list + [4, 5]
        return super(KlassMeta, cls).__new__(cls, name, bases, attrs)

class SubKlass(Klass):
    __metaclass__ = KlassMeta

С Python 3 вы объявите метакласс с использованием нового синтаксиса:

class SubKlass(Klass, metaclass=KlassMeta):
    pass

Ответ 4

От тела класса нет никакого способа; из контекста, в котором я сам доступен (или самого класса), вы можете изучить __bases__.

Ответ 5

Как вы видели из других ответов, таких как katriealex's: Нет, вы не можете. По крайней мере, не легко.

Q: Что вы на самом деле пытаетесь сделать?

В: Почему вы хотите сделать это?