Переопределяющая переменная пространства имен родителей

Я пытаюсь понять классы Python, прежде чем углубиться в метаклассы. Я наткнулся на код, который не могу понять. В этой ситуации классы используют не self, а скорее пространства имен классов (нет возможности использовать self, поэтому вопрос здесь). Как я могу определить в одном классе некоторые переменные пространства имен, а затем переопределить значение, от которого они все зависят, в дочерних классах?

Первый случай

class B():
    potato = "hey"
    test = potato
    #here would go a lot of more code that just depends on that potato value

class c(B):
    B.potato = "not hey"

c_potato = c().test
print(c_potato)

Это печатает эй . Это я понимаю, потому что test указывает на строку «эй», которая не является изменяемой. Изменение B.potato = "not hey" только изменяет пространство имен класса potato на новую строку, но не меняет то, на что указывает тест . Итак, я подумал: а что если я сделаю это со списком, это по ссылке, верно?

class B():
    potato = ["hey"]
    test = potato[0]

class c(B):
    B.potato[0] = "not hey"

c_potato = c().test
print(c_potato)

На мой взгляд, это должно было сработать. Я не изменил то, на что указывает картофель , а скорее на ценность. Нет? Но я понимаю, что на самом деле это не сработает, потому что test указывает на potato [0], а не просто potato . Так что да, я понимаю, почему это также печатает эй .

Тогда я понял, что если тест должен указывать на результат, который не является изменяемым, то то, что я пытаюсь сделать с пространствами имен, невозможно.

class B():

    @staticmethod
    def get_potato():
      return "hey"

    potato = get_potato.__func__
    test = potato()

class c(B):

    @staticmethod
    def get_potato():
      return "not hey"

    B.potato = "6"

c_potato = c().test
print(c_potato)

Я изменил здесь все значение B.potato, но к настоящему моменту тест уже указывает на результат родительской функции potato () , так что он не имеет значения и все еще выводит «эй» .

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

class Meta(type):
    def __new__(cls, name, bases, attrs):
        x = super().__new__(cls, name, bases, attrs)
        x.potato = "basic hey"
        if 'meta_args' in attrs:
            x.potato = attrs['meta_args'][0]
            del attrs['meta_args'] # clean up
        x.test = x.potato    
        return x

class A():
  pass

class B(A, metaclass=Meta):
  meta_args = ["super hey"]
  pass

class C(B):
    meta_args = ["not hey"]

b = B().test
c = C().test
print(b)
print(c)

И это правильно печатает супер эй для б, а не эй для с . Вопрос в том, можно ли это сделать без метаклассов? Мой мозг болит в этот момент.

Всего 2 ответа


Как вам показалось, вы много экспериментировали с «удивительными» результатами, с простыми вещами, такими как присвоение первого элемента списка переменной, чтобы освободить вас от неправильных представлений об этой проблеме.

Почему вы думаете, что «прибили» это и поняли правильно, когда вы идете дальше простого и «узнаете», что можете «изменить значение» с помощью метакласса?

Нет - просто продолжая свои прежние тенденции, вы по-прежнему ошибаетесь.

Код в метаклассе __new__ , так же как код в теле класса запускается один раз при создании каждого класса. Переменные создаются, присваиваются и не изменяются после этого.

Ваш «эксперимент с метаклассом» создает новое значение для атрибутов potato и test , устанавливая затем новый класс во время создания этого нового класса (C).

В этом нет ничего «мета», что отличалось бы от выполнения:

class B:
    potato = 0

class C(B):
   pass

C.potato = 1

print(C.potato)   # Hey it works!

Если вам нужны «волшебные изменяющие значения», ведущие себя так же, как вычисляемые значения в программах для работы с электронными таблицами, в Python есть механизм для этого, и он не связан с метаклассами: это «дескрипторы». Из них наиболее известными являются созданные со встроенным property .

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

Но так как они предназначены для использования, а не для игры с самой популярной формой, property предназначено для работы с экземплярами классов, а не для глупого использования классов в качестве просто пространств имен.

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

class B:
    def __init__(self):
        self.potato = Hey

    @property
    def test(self):
        return self.potato

c = B()
print(c.test)
c.potato = "not hey"
print(c.test)

Вы, вероятно, хотите @property :

class B:
    potato = 'hey'

    @property
    def test(self):
        return self.potato


class C(B):
    potato = 'not hey'

Что вы сделали, так это назначили "hey" для test тем или иным способом. Его значение не изменится, если вы на самом деле не назначите что-то еще для test . Делая его @property , функция def test вызывается каждый раз, когда вы .test к .test , поэтому его значение может быть вычислено динамически; в этом случае исходя из стоимости potato . Подкласс объявляет свою собственную potato , которая скрывает свойство potato родителя.


Есть идеи?

10000