Python: как разделить атрибут класса между производными классами? - PullRequest
0 голосов
/ 04 мая 2018

Я хочу поделиться некоторой информацией между всеми экземплярами некоторого класса и всеми его производными классами.

class Base():
    cv = "some value"  # information I want to share

    def print_cv(self, note):
        print("{}: {}".format(note, self.cv))

    @classmethod
    def modify_cv(cls, new_value):
        # do some class-specific stuff
        cls.cv = new_value

class Derived(Base):
    pass

b = Base()
d = Derived()

b.print_cv("base")
d.print_cv("derived")

Вывод соответствует ожиданиям (экземпляры обоих классов видят правильный атрибут класса):

base: some value
derived: some value

Я могу изменить значение этого атрибута класса, и все по-прежнему в порядке:

# Base.cv = "new value"
b.modify_cv("new value")
b.print_cv("base")       # -> base: new value
d.print_cv("derived")    # -> derived: new value

Пока все хорошо. Проблема в том, что «соединение» между базовыми и производными классами может быть разорвано, если я получу доступ к cv через производный класс:

# Derived.cv = "derived-specific value"
d.modify_cv("derived-specific value")
b.print_cv("base")       # -> base: new value
d.print_cv("derived")    # -> derived: derived-specific value

Такое поведение ожидается, но это не то, что я хочу!

Я понимаю, почему a и b видят разные значения cv - потому что они являются экземплярами разных классов. Я переопределил значение cv в производном классе, и теперь производный класс ведет себя по-другому, я использовал эту функцию много раз.

Но для моей текущей задачи мне нужно, чтобы a и b всегда использовали один и тот же cv!

UPDATE

Я обновил вопрос, и теперь он лучше описывает реальную ситуацию. На самом деле я не изменил значение cv следующим образом:

Base.cv = "new value"

модификации были сделаны в некоторых методах класса (фактически все эти методы класса были реализованы в Base классе).

И теперь решение стало очевидным, мне просто нужно немного изменить метод:

class Base():
    @classmethod
    def modify_cv(cls, new_value):
        #cls.cv = new_value
        Base.cv = new_value

Спасибо всем за обсуждение и идеи (вначале я собирался использовать методы получения / установки и атрибут уровня модуля)

Ответы [ 3 ]

0 голосов
/ 04 мая 2018

classmethod полезно, когда вам нужно знать, какой класс вызывает метод, но если вам нужно одинаковое поведение независимо от класса, вызывающего метод, вы можете использовать staticmethod вместо этого. Затем вы можете получить доступ к переменной класса просто через имя базового класса с помощью Base.cv:

class Base:
    cv = "some value"  # information I want to share

    def print_cv(self, note):
        print("{}: {}".format(note, self.cv))

    @staticmethod
    def modify_cv(new_value):
        Base.cv = new_value

Вы можете вызывать его для любого экземпляра или подкласса, но он всегда меняется Base.cv:

>>> b = Base()
>>> d = Derived()
>>> Base.cv == Derived.cv == b.cv == d.cv == "some value"
True
>>> d.modify_cv("new value")
>>> Base.cv == Derived.cv == b.cv == d.cv == "new value"
True

Обновление:

Если вам по-прежнему требуется доступ к классу по другим причинам, используйте classmethod с аргументом cls, как вы делали это раньше, но по-прежнему обращайтесь к переменной базового класса через Base.cv, а не cls.cv:

@classmethod
def modify_cv(cls, new_value):
    do_stuff_with(cls)
    Base.cv = new_value
0 голосов
/ 04 мая 2018

В Python внутри метода вы можете использовать пустое имя переменной __class__ для обозначения фактического класса, в котором определен метод.

Это отличается от cls аргумента, который передается методам класса, или self.__class__ для обычных методов, которые будут ссылаться на подкласс, если метод вызывается в подклассе. Таким образом, cls.attr = value установит значение для класса подкласса __dict__, и значение атрибута будет независимым для этого подкласса с этого момента. Это то, что вы получаете там.

Вместо этого вы можете использовать:

class MyClass:
   cv  = "value"

   @classmethod # this is actually optional
   def modify_cv(cls, new_value):
        __class__.cv = new_value

__class__ создается автоматически в Python 3 механизм, который позволяет писать Форма без параметров super

0 голосов
/ 04 мая 2018

Вы должны переопределить __setattr__ класса класса, то есть метакласса:

class InheritedClassAttributesMeta(type):
    def __setattr__(self, key, value):
        cls = None

        if not hasattr(self, key):
            # The attribute doesn't exist anywhere yet,
            # so just set it here
            cls = self
        else:
            # Find the base class that's actually storing it
            for cls in self.__mro__:
                if key in cls.__dict__:
                    break
        type.__setattr__(cls, key, value)


class Base(metaclass=InheritedClassAttributesMeta):
    cv = "some value"


class Derived(Base):
    pass


print(Derived.cv)
Derived.cv = "other value"
print(Base.cv)

Использование метаклассов часто излишне, поэтому лучше указать Base.

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

...