Неинтуитивное поведение переменной члена Python - PullRequest
2 голосов
/ 30 сентября 2011

Этот скрипт:

class testa():
    a = []

class testb():
    def __init__(self):
        self.a = []

ta1 = testa(); ta1.a.append(1); ta2 = testa(); ta2.a.append(2)
tb1 = testb(); tb1.a.append(1); tb2 = testb(); tb2.a.append(2)

print ta1.a, ta2.a, tb1.a, tb2.a

производит этот вывод:

[1, 2] [1, 2] [1] [2]

но я ожидал

[1] [2] [1] [2]

Почему я был не прав? Определения testa и testb мне кажутся эквивалентными, так почему же поведение должно так сильно меняться?!

РЕДАКТИРОВАТЬ: Это кажется не интуитивно понятным, потому что это отличается от того, как ведут себя другие типы, такие как int и str. По какой-то причине списки создаются как переменные класса, когда они не инициализируются в init , но целые и строковые значения создаются как переменные объекта независимо от того, что.

Ответы [ 4 ]

7 голосов
/ 30 сентября 2011

В testa переменная a является переменной класса и является общей для всех экземпляров.ta1.a и ta2.a относятся к одному и тому же списку.

В testb переменная a является объектной переменной .Каждый экземпляр имеет свое собственное значение.

Подробнее см. Переменные класса и объекта .

4 голосов
/ 30 сентября 2011

Одна переменная класса, другая переменная экземпляра.
Классовые переменные являются общими для всех членов класса, экземпляры экземпляров уникальны для каждого экземпляра.
http://docs.python.org/tutorial/classes.html

3 голосов
/ 30 сентября 2011

Помните, что оператор class в Python намного ближе к любому другому оператору блока, чем в случае языков, подобных C ++.

В Python оператор class содержит нескольковыполняемые операторы, такие как def, if или while.Разница только в том, что интерпретатор делает с блоком.В случае операторов блока управления потоком, таких как if и while, интерпретатор выполняет блок в соответствии со значением оператора управления потоком.В def интерпретатор сохраняет блок и выполняет его всякий раз, когда вызывается объект функции.

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

Итак, для этого:

class testa():
    a = []

Python выполняет блок a = [].Затем в конце область действия содержит a, привязанную к пустому объекту списка.Так вот что у вас в классе .Это не какой-то конкретный экземпляр класса, а сам класс.

Он наследует конструктор бездействия от object, поэтому при создании экземпляра класса с помощью ta1 = testa() вы получите пустой экземпляр.Затем, когда вы запрашиваете ta1.a, Python не находит переменную экземпляра с именем a (потому что ta1 вообще не имеет переменных экземпляра), и поэтому он ищет a в классе testa.Это, конечно, он находит;но он находит один и тот же для каждого экземпляра testa, и поэтому вы получаете поведение, которое вы наблюдали.

С другой стороны, это:

class testb():
    def __init__(self):
        self.a = []

полностью отличается.Здесь после того, как блок класса был выполнен, содержимое области видимости снова представляет собой одно имя, но на этот раз оно __init__ связано с функцией.Это становится содержанием класса.

Теперь, когда вы создаете экземпляр этого класса с помощью testb(), Python находит __init__ в классе и вызывает эту функцию с новым экземпляром.При выполнении этой функции в новом экземпляре создается переменная a instance .Таким образом, каждый экземпляр testb() получает свою собственную переменную, и вы наблюдаете поведение.

Возьмите домашнее сообщение: блок class в Python - это , а не просто набор объявленийвещи, которые содержатся в экземплярах этого класса, в отличие от традиционных языков программирования, таких как C ++ и Java.Это фактический код, который фактически выполняется для определения содержимого класса.Это может быть очень удобно: вы можете использовать операторы if, результаты выполнения функций и все, что вы будете использовать в любом другом контексте, внутри тела класса, чтобы решить, что определить в вашем классе.

( NOTE : Раньше я лгал для простоты. Даже экземпляры testa будут иметь некоторые переменные экземпляра, потому что есть некоторые, которые автоматически создаются по умолчанию для всех экземпляров, но вы не видите их так часто в течение дня.современный Python).

0 голосов
/ 30 сентября 2011

Определения testa и testb мне кажутся эквивалентными, так почему же поведение должно так резко меняться?!

Поведение Python полностью логично - более того, чем в Java, C ++ или C #, в этом все, что вы объявляете в области действия class, принадлежит классу.

В Python класс является логически шаблоном для объектов, но на самом деле это не физически. Самое близкое к тому, что вы можете указать, что «экземпляры имеют именно этот набор членов», это наследовать от object, перечислить их в классе '__slots__ и инициализировать их в __init__. Однако Python не заставит вас устанавливать все значения в __init__, а те, которые вы не установили, не получат никаких значений по умолчанию (попытка получить к ним доступ приведет к AttributeError), и вы даже можете явно удалить атрибуты из экземпляров позже (через del). Все, что делает __init__, запускается на каждом новом экземпляре (это удобный и идиоматический способ задания начальных значений для атрибутов). Все, что делает __slots__, - это предотвращает добавление других атрибутов.

Это кажется не интуитивным, потому что оно отличается от того, как ведут себя другие типы, такие как int и str. По какой-то причине списки создаются как переменные класса, когда они не инициализируются в init, но целочисленные значения и strs создаются как переменные объекта независимо от того, что.

Это не тот случай.

Если вы положите туда int или str, он все равно будет членом класса. Если бы вы могли как-то изменить int или str, то эти изменения были бы отражены в каждом объекте, так же, как и в списке, потому что поиск атрибута в объекте нашел бы его в классе. (Грубо говоря, атрибут access сначала проверяет объект, затем класс.)

Однако, вы на самом деле не можете этого сделать . То, что вы считаете эквивалентным тестом, вовсе не эквивалентно, потому что ваш тест с int или str вызывает присвоение атрибуту объекта, который скрывает атрибут класса. ta1.a.append вызывает метод списка, который вызывает изменение внутренних элементов списка. Это невозможно для строк и целых чисел Python. Даже если вы напишите что-то вроде ta1.n += 1, это преобразуется в ta.n = ta.n + 1, где атрибут ищется (и обнаруживается в классе), вычисляется сумма, а затем присваивается обратно ta.n (назначение всегда обновляет объект, если явно не запрещено классом '__slots__, в этом случае возникает исключение).

Вы можете легко продемонстрировать, что значение принадлежит классу, получив к нему доступ непосредственно из класса , например, testa.a.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...