В чем разница между переменными класса и экземпляра? - PullRequest
58 голосов
/ 22 января 2012

В чем разница между переменными класса и экземпляра в Python?

class Complex:
    a = 1

и

class Complex:
    def __init__(self):
        self.a = 1

Использование вызова: x = Complex().a в обоих случаях присваивает x 1.

Буду признателен за более подробный ответ о __init__() и self.

Ответы [ 2 ]

104 голосов
/ 22 января 2012

Когда вы пишете блок класса, вы создаете атрибуты класса (или переменные класса).Все имена, которые вы назначаете в блоке класса, включая методы, которые вы определяете с помощью def, становятся атрибутами класса.

После создания экземпляра класса все, что имеет ссылку на экземпляр, может создавать в нем атрибуты экземпляра.Внутри методов «текущий» экземпляр почти всегда связан с именем self, поэтому вы думаете о них как о «собственных переменных».Обычно в объектно-ориентированном проектировании предполагается, что код, прикрепленный к классу, должен контролировать атрибуты экземпляров этого класса, поэтому почти все присвоение атрибутов экземпляров выполняется внутри методов с использованием ссылки на экземпляр, полученный в selfпараметр метода.

Атрибуты класса часто сравнивают с статическими переменными (или методами), которые можно найти в таких языках, как Java, C # или C ++.Однако, если вы хотите достичь более глубокого понимания, я бы не думал, что атрибуты класса «совпадают» со статическими переменными.Хотя они часто используются для одних и тех же целей, основная концепция совершенно иная.Подробнее об этом в разделе «Дополнительно» под строкой.

Пример!

class SomeClass:
    def __init__(self):
        self.foo = 'I am an instance attribute called foo'
        self.foo_list = []

    bar = 'I am a class attribute called bar'
    bar_list = []

После выполнения этого блока существует класс SomeClass с 3 атрибутами класса: __init__, bar и bar_list.

Затем мы создадим экземпляр:

instance = SomeClass()

Когда это произойдет, будет выполнен метод SomeClass '__init__, получая новый экземпляр в его параметре self.Этот метод создает два атрибута экземпляра: foo и foo_list.Затем этот экземпляр присваивается переменной instance, поэтому он связан с чем-то с этими двумя атрибутами экземпляра: foo и foo_list.

Но:

print instance.bar

дает:

I am a class attribute called bar

Как это случилось?Когда мы пытаемся извлечь атрибут через точечный синтаксис, а атрибут не существует, Python делает несколько шагов, чтобы попытаться выполнить ваш запрос в любом случае.Следующая вещь, которую он попробует, - это посмотреть на атрибуты класса класса вашего экземпляра.В этом случае он нашел атрибут bar в SomeClass, поэтому он вернул его.

Кстати, вызовы методов также работают.Например, при вызове mylist.append(5) mylist не имеет атрибута с именем append.Но класс из mylist делает, и он привязан к объекту метода.Этот объект метода возвращается битом mylist.append, а затем бит (5) вызывает метод с аргументом 5.

. Это полезно, когда все экземплярыSomeClass будет иметь доступ к тому же атрибуту bar.Мы могли бы создать миллион экземпляров, но нам нужно только сохранить эту одну строку в памяти, потому что все они могут ее найти.

Но вы должны быть немного осторожнее.Посмотрите на следующие операции:

sc1 = SomeClass()
sc1.foo_list.append(1)
sc1.bar_list.append(2)

sc2 = SomeClass()
sc2.foo_list.append(10)
sc2.bar_list.append(20)

print sc1.foo_list
print sc1.bar_list

print sc2.foo_list
print sc2.bar_list

Как вы думаете, что это печатает?

[1]
[2, 20]
[10]
[2, 20]

Это потому, что каждый экземпляр имеет свою собственную копию foo_list, поэтому они былиприлагается отдельно.Но все экземпляры имеют общий доступ к одному и тому же bar_list.Поэтому, когда мы сделали sc1.bar_list.append(2), это затронуло sc2, хотя sc2 еще не существовало!И также sc2.bar_list.append(20) повлияло на bar_list, полученное через sc1.Это часто не то, что вы хотите.


Далее следует углубленное изучение.:)

Чтобы по-настоящему взбодриться на Python, исходя из традиционных ОО-языков со статической типизацией, таких как Java и C #, вы должны научиться немного переосмысливать классы.

В Java класс на самом деле не вещь сама по себе.Когда вы пишете класс, вы скорее объявляете кучу вещей, которые объединяют все экземпляры этого класса.Во время выполнения есть только экземпляры (и статические методы / переменные, но на самом деле это просто глобальные переменные и функции в пространстве имен, связанном с классом, на самом деле не имеющем ничего общего с ОО).Классы - это способ, которым вы записываете в исходном коде, какими будут экземпляры во время выполнения;они только «существуют» в вашем исходном коде, а не в работающей программе.

В Python класс не является чем-то особенным.Это объект, как и все остальное.Таким образом, «атрибуты класса» фактически идентичны «атрибутам экземпляра»;на самом деле есть только «атрибуты».Единственная причина для проведения различия состоит в том, что мы склонны использовать объекты, которые являются классами, отличными от объектов, которые не являются классами.Базовый механизм все тот же.Вот почему я говорю, что было бы ошибкой считать атрибуты класса статическими переменными из других языков.

Но вещь, которая действительно отличает классы Python от классов в стиле Java, заключается в том, что, как и любой другой объект каждый класс является экземпляром некоторого класса !

В Python большинство классов являются экземплярами встроенного класса с именем type.Именно этот класс контролирует общее поведение классов и делает все вещи ОО такими, как он.По умолчанию ОО-способ иметь экземпляры классов, которые имеют свои собственные атрибуты и имеют общие методы / атрибуты, определенные их классом, - это просто протокол в Python.Вы можете изменить большинство аспектов этого, если хотите.Если вы когда-либо слышали об использовании метакласса , все, что нужно, это определить класс, который является экземпляром другого класса, чем type.

Единственная действительно «особенная» вещьО классах (кроме всех встроенных механизмов, заставляющих их работать так, как они работают по умолчанию), есть синтаксис блоков классов, чтобы упростить создание экземпляров type.Это:

class Foo(BaseFoo):
    def __init__(self, foo):
        self.foo = foo

    z = 28

примерно эквивалентно следующему:

def __init__(self, foo):
    self.foo = foo

classdict = {'__init__': __init__, 'z': 28 }

Foo = type('Foo', (BaseFoo,) classdict)

И он организует, чтобы все содержимое classdict стало атрибутами создаваемого объекта.

Итак, становится почти тривиально увидеть, что вы можете получить доступ к атрибуту класса с помощью Class.attribute так же легко, как и i = Class(); i.attributei, и Class являются объектами, а объекты имеют атрибуты.Это также упрощает понимание того, как можно изменить класс после его создания;просто присвойте его атрибуты так же, как и с любым другим объектом!

На самом деле экземпляры не имеют особых особых отношений с классом, используемым для их создания.Python знает, какой класс для поиска атрибутов, которые не найдены в экземпляре, является скрытым атрибутом __class__.Который вы можете прочитать, чтобы узнать, к какому классу относится этот экземпляр, как и к любому другому атрибуту: c = some_instance.__class__.Теперь у вас есть переменная c, связанная с классом, хотя она, вероятно, не имеет того же имени, что и класс.Вы можете использовать это, чтобы получить доступ к атрибутам класса, или даже вызвать его, чтобы создать больше его экземпляров (даже если вы не знаете, что это за класс!).

И вы даже можете назначить i.__class__, чтобы изменить класс, к которому он относится! Если вы сделаете это, ничего особенного не произойдет немедленно. Это не потрясающее. Все это означает, что когда вы ищите атрибуты, которые не существуют в экземпляре, Python будет смотреть на новое содержимое __class__. Поскольку это включает в себя большинство методов, и методы обычно ожидают, что экземпляр, с которым они работают, находится в определенных состояниях, это обычно приводит к ошибкам, если вы делаете это случайным образом, и это очень запутанно, но это можно сделать. Если вы очень осторожны, то, что вы храните в __class__, не обязательно должно быть объектом класса; все, что Python сделает с этим, - это поиск атрибутов при определенных обстоятельствах, поэтому все, что вам нужно, это объект, который имеет правильные атрибуты (за исключением некоторых оговорок, когда Python разборчив в вещах, являющихся классами или экземплярами определенного класса).

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

10 голосов
/ 22 января 2012

То, что вы называете переменной «экземпляра», на самом деле не является переменной экземпляра; это переменная класса . См. справочник по языкам о классах .

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

>>> class Complex:
>>>    a = []
>>>
>>> b = Complex()
>>> c = Complex()
>>> 
>>> # What do they look like?
>>> b.a
[]
>>> c.a
[]
>>> 
>>> # Change b...
>>> b.a.append('Hello')
>>> b.a
['Hello']
>>> # What does c look like?
>>> c.a
['Hello']

Если бы вы использовали self, то это была бы истинная переменная экземпляра, и, таким образом, каждый экземпляр имел бы свой уникальный a. Функция __init__ объекта вызывается , когда создается новый экземпляр , а self является ссылкой на этот экземпляр.

...