Ответ 1
Хорошо, сначала сначала.
В Python нет такой вещи, как "объявление переменной" или "инициализация переменных".
Существует просто то, что мы называем "присваиванием", но, вероятно, нужно просто называть "именование".
Назначение означает, что "это имя в левой части теперь относится к результату оценки правой части, независимо от того, на что оно ссылалось раньше (если что-либо)".
foo = 'bar' # the name 'foo' is now a name for the string 'bar'
foo = 2 * 3 # the name 'foo' stops being a name for the string 'bar',
# and starts being a name for the integer 6, resulting from the multiplication
Как таковые, имена Python (лучший термин, чем "переменные", возможно) не имеют связанных типов; значения делают. Вы можете повторно применить одно и то же имя к чему бы то ни было, независимо от его типа, но все еще имеет поведение, зависящее от его типа. Имя - это просто способ ссылаться на значение (объект). Это отвечает на ваш второй вопрос: вы не создаете переменные для хранения пользовательского типа. Вы не создаете переменные для хранения какого-либо определенного типа. Вы вообще не создаете переменные. Вы даете имена объектам.
Вторая точка: Python следует очень простому правилу, когда дело доходит до классов, что на самом деле гораздо более согласовано, чем такие языки, как Java, С++ и С#: все, объявленное внутри блока class
, является частью класс. Таким образом, функции (def
), написанные здесь, - это методы, то есть часть объекта класса (не хранящиеся на основе каждого экземпляра), как и в Java, С++ и С#; но другие имена здесь также часть класса. Опять же, имена - это просто имена, и у них нет связанных типов, а - объекты тоже в Python. Таким образом:
class Example:
data = 42
def method(self): pass
Классы также являются объектами, в Python.
Итак, теперь мы создали объект с именем Example
, который представляет класс всех вещей Example
s. Этот объект имеет два атрибута для пользователя (в С++, "члены", в С#, "поля или свойства или методы", в Java "поля или методы" ). Один из них называется data
, и он сохраняет целочисленное значение 42
. Другой - method
, и он сохраняет объект функции. (Есть еще несколько атрибутов, которые автоматически добавляет Python.)
Эти атрибуты все еще не являются частью объекта. По сути, объект представляет собой просто набор имен (имена атрибутов), пока вы не перейдете к вещам, которые больше нельзя разделить. Таким образом, значения могут быть разделены между разными экземплярами класса или даже между объектами разных классов, если вы намеренно установили это.
Позвольте создать экземпляр:
x = Example()
Теперь у нас есть отдельный объект с именем x
, который является экземпляром Example
. data
и method
на самом деле не являются частью объекта, но мы все еще можем просмотреть их через x
из-за некоторой магии, которую Python делает за кулисами. Когда мы смотрим вверх method
, в частности, вместо этого мы получим "связанный метод" (когда мы его назовем, x
автоматически передается как параметр self
, что не может произойти, если мы напрямую посмотрим Example.method
).
Что происходит, когда мы пытаемся использовать x.data
?
Когда мы исследуем его, он сначала просматривает объект. Если он не найден в объекте, Python выглядит в классе.
Однако, когда мы назначаем x.data
, Python создаст атрибут объекта. Это заменит атрибут не.
Это позволяет нам выполнить инициализацию object. Python автоматически вызовет метод класса __init__
в новых экземплярах, если они созданы, если они есть. В этом методе мы можем просто назначить атрибутам для установки начальных значений для этого атрибута для каждого объекта:
class Example:
name = "Ignored"
def __init__(self, name):
self.name = name
# rest as before
Теперь мы должны указать name
при создании Example
, и каждый экземпляр имеет свой собственный name
. Python будет игнорировать атрибут class Example.name
всякий раз, когда мы просматриваем .name
экземпляра, потому что сначала будет найден атрибут экземпляра.
Последнее предупреждение: модификация (мутация) и назначение - это разные вещи!
В Python строки неизменяемы. Они не могут быть изменены. Когда вы выполните:
a = 'hi '
b = a
a += 'mom'
Вы не меняете исходную строку "привет". Это невозможно в Python. Вместо этого вы создаете строку new 'hi mom'
и вызываете a
, чтобы перестать быть именем для 'hi '
и вместо этого начинать имя для 'hi mom'
. Мы создали b
имя для 'hi '
, а после повторного применения имени a
b
по-прежнему является именем для 'hi '
, потому что 'hi '
все еще существует и не был изменен.
Но списки можно изменить:
a = [1, 2, 3]
b = a
a += [4]
Теперь b
также [1, 2, 3, 4], потому что мы сделали b
имя для той же вещи, что и a
, и затем мы изменили эту вещь. Мы не создали новый список для a
для имени, потому что Python просто обрабатывает +=
по-разному для списков.
Это относится к объектам, потому что, если у вас есть список как атрибут класса и используется экземпляр для изменения списка, это изменение будет "видно" во всех других случаях. Это связано с тем, что (а) данные фактически являются частью объекта класса, а не объектом экземпляра; (b) поскольку вы изменяли список и не выполняли простое назначение, вы не создали атрибут нового экземпляра, скрывающий атрибут класса.