Сумасшедшая изменчивость Python

Что должен напечатать этот код?

class Parent():
    class Meta(object):
        classattr = "Hello"

class Child(Parent):
    pass


Child.Meta.classattr = "world"


ch = Child()
pr = Parent()

ch.Meta.classattr = "Oppa"

print Parent.Meta.classattr
print Child.Meta.classattr

print pr.Meta.classattr
print ch.Meta.classattr

Я ожидал следующее:

Hello
world
Hello
Oppa

Но получил

Oppa
Oppa
Oppa
Oppa

Итак... я могу изменить родительский класс (не экземпляр!), изменив экземпляр дочернего класса. Это нормально?


person Makc    schedule 23.12.2013    source источник


Ответы (3)


Child.Meta на самом деле является Parent.Meta - класс Child не имеет собственного атрибута класса Meta, поэтому правила поиска атрибута разрешаются в Parent.Meta. Кроме того, поскольку это атрибут класса, любой поиск экземпляра Parent (включая экземпляры Child) будет разрешаться в тот же самый объект класса.

person bruno desthuilliers    schedule 23.12.2013
comment
Что легко проверить, запустив ch.Meta is pr.Meta. - person Hannes Ovrén; 23.12.2013
comment
Хорошо, но я пытался использовать глубокую копию, и ничего не изменилось: import copy class Parent(object): class IntClass: intclassattr = ["Hello"] Child = copy.deepcopy(Parent) Child.IntClass.intclassattr.append("world") print Parent.IntClass.intclassattr результат ['Hello', 'world'] Но глубокая копия должна рекурсивно копировать все поля объекта, как я могу наследовать от Родителя и изменять поля класса, не изменяя его в экземплярах Родителя? - person Makc; 23.12.2013
comment
Из документа модуля copy: эта версия не копирует такие типы, как модуль, класс, функция, метод (...). Вы можете проверить это после того, как Child = copy.deepcopy(Parent), Child is Parent примет значение True. - person bruno desthuilliers; 23.12.2013

Я попытаюсь объяснить, что здесь происходит:

class Parent():
    class Meta(object):
        classattr = "Hello"

class Child(Parent):
    pass


Child.Meta.classattr = "world"

Здесь вы назначаете «мир» classattr мета. Неважно, был ли Meta изначально определен внутри Parent; Дочерний и родительский объекты имеют одно и то же Meta.

ch = Child()
pr = Parent()

ch.Meta.classattr = "Oppa"

Здесь вы назначаете «мир» classattr мета. Неважно, является ли ch экземпляром. Мета всегда один и тот же объект.

print Parent.Meta.classattr
print Child.Meta.classattr

print pr.Meta.classattr
print ch.Meta.classattr

Здесь вы печатаете classattr той же меты.

person smeso    schedule 23.12.2013

Я думаю, самое простое объяснение состоит в том, что метакласс — это просто обычный объект (типа type), на который ссылается Parent, а создание подклассов и создание экземпляров не копируют метакласс, а просто создают новую ссылку на него (или, строго говоря, даже не это— именно так работает поиск атрибутов). Таким образом, каждый раз, когда вы сохраняете новое значение в classattr, вы фактически изменяете тот же атрибут того же объекта.

person Erik Kaplun    schedule 23.12.2013