Ответ 1
Когда вы пишете блок класса, вы создаете атрибуты класса (или переменные класса). Все имена, которые вы назначили в блоке классов, включая методы, которые вы определяете с помощью def
, становятся атрибутами класса.
После создания экземпляра класса все, что ссылается на экземпляр, может создавать на нем атрибуты экземпляра. Внутри методов "текущий" экземпляр почти всегда связан с именем self
, поэтому вы думаете об этом как о "самопеременных переменных". Обычно в объектно-ориентированном дизайне код, прикрепленный к классу, должен иметь контроль над атрибутами экземпляров этого класса, поэтому почти все атрибуты атрибутов экземпляра выполняются внутри методов, используя ссылку на экземпляр, полученный в self
параметр метода.
Атрибуты класса часто сравниваются со статическими переменными (или методами), которые можно найти на таких языках, как Java, С# или С++. Однако, если вы хотите стремиться к более глубокому пониманию, я бы избегал думать о атрибутах класса как о "тех же", что и статические переменные. Хотя они часто используются для одних и тех же целей, базовая концепция совершенно иная. Подробнее об этом в разделе "Расширенный" ниже строки.
Пример!
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
с тремя атрибутами класса: __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, исходя из традиционных статически типизированных OO-языков, таких как Java и С#, вам нужно немного научиться переосмысливать классы.
В Java класс не является само по себе. Когда вы пишете класс, вы больше объявляете кучу вещей, которые имеют все экземпляры этого класса. Во время выполнения есть только экземпляры (и статические методы/переменные, но на самом деле это просто глобальные переменные и функции в пространстве имен, связанных с классом, не имеет ничего общего с OO). Классы - это то, как вы записываете в своем исходном коде, какими будут экземпляры во время выполнения; они только "существуют" в исходном коде, а не в запущенной программе.
В Python класс ничего особенного. Это объект, как и все остальное. Таким образом, "атрибуты класса" на самом деле являются точно такими же, как "атрибуты экземпляра"; на самом деле там просто "атрибуты". Единственная причина для разграничения состоит в том, что мы склонны использовать объекты, которые являются классами, отличными от объектов, которые не являются классами. Основополагающий механизм все тот же. Вот почему я говорю, что было бы ошибкой думать о атрибутах класса как статических переменных с других языков.
Но то, что действительно делает классы Python отличными от классов стиля Java, заключается в том, что, как и любой другой объект , каждый класс является экземпляром некоторого класса!
В Python большинство классов являются экземплярами встроенного класса с именем type
. Именно этот класс контролирует общее поведение классов и делает все OO файлы такими, как он. Способ OO по умолчанию для экземпляров классов, которые имеют свои собственные атрибуты, и имеет общие методы/атрибуты, определенные их классом, является всего лишь протоколом на 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.attribute
. Оба i
и Class
являются объектами, а объекты имеют атрибуты. Это также позволяет легко понять, как вы можете изменить класс после его создания; просто назначьте его атрибуты так же, как и с любым другим объектом!
Фактически, экземпляры не имеют особых особых отношений с классом, используемым для их создания. Способ, которым Python знает, какой класс ищет атрибуты, которые не найдены в экземпляре, - это скрытый атрибут __class__
. Что вы можете прочитать, чтобы узнать, какой класс это экземпляр, так же как и с любым другим атрибутом: c = some_instance.__class__
. Теперь у вас есть переменная c
, связанная с классом, хотя она, вероятно, не имеет того же имени, что и класс. Вы можете использовать это для доступа к атрибутам класса или даже вызвать его для создания большего количества экземпляров (даже если вы не знаете, какой класс он есть!).
И вы даже можете назначить i.__class__
, чтобы изменить класс, которым он является экземпляр! Если вы это сделаете, ничего особенного не произойдет немедленно. Это не разрушительно. Все это означает, что при поиске атрибутов, которые не существуют в экземпляре, Python пойдет посмотреть новое содержимое __class__
. Поскольку это включает большинство методов, и методы обычно ожидают, что экземпляр, в котором они работают, находятся в определенных состояниях, обычно это приводит к ошибкам, если вы делаете это наугад, и это очень запутанно, но это можно сделать. Если вы очень осторожны, то вещь, которую вы храните в __class__
, даже не должна быть объектом класса; все Python собирается с ним искать атрибуты при определенных обстоятельствах, поэтому все, что вам нужно, это объект, который имеет правильные типы атрибутов (некоторые оговорки в сторону, где Python действительно придирчивы к вещам, являющимся классами или экземплярами определенного класса).
Скорее всего, сейчас. Надеюсь (если вы даже зачитали это далеко), я не слишком вас смутил. Python опрятен, когда вы узнаете, как это работает.:)