Переменные и присваивание
Переменные в Python
В императивной модели переменная — именованная память; в Python имя — ссылка на объект, тип принадлежит объекту (динамическая типизация).
Области видимости, LEGB, модули как пространства имён — 4.03, типизация, builtins и типизация.
Особенности переменной в Python
На первых шагах переменную удобно представить как коробочку с наклейкой-именем, куда кладут значение. Так проще понять присваивание name = "Анна". На более глубоком уровне в Python всё устроено иначе: переменная — это имя (ссылка) на объект в памяти, а не "ящик" с типом.
Важно понять, что в большинстве языков программирования переменная является "ящиком", в котором хранится значение. В Python — это ссылка на объект.
Когда мы пишем:
x = 42
…то мы не сохраняем 42 в x, а говорим интерпретатору создать объект типа int со значением 42 и связать с ним имя x. Пошагово:
- сначала создается объект
int(42); - затем имя
xв текущем namespace начинает ссылаться на этот объект; - при следующем присваивании ссылка
xперенаправится на другой объект.
То есть, переменная не имеет типа, тип есть у объекта, на который она ссылается. Одна и та же переменная может ссылаться на объекты разных типов, и несколько переменных могут ссылаться на один и тот же объект.
a = [1, 2, 3]
b = a # b теперь ссылается на тот же список
b.append(4)
print(a) # [1, 2, 3, 4] — изменился и a!
a и b - две ссылки на один и тот же изменяемый объект. Разбор ключевых элементов:
b = aсоздает второе имя для того же списка;append(4)меняет содержимое объектаlistна месте;print(a)показывает изменение, потому чтоaиbразделяют один объект.
Констант в Python нет, существуют лишь общие соглашения об именовании, когда переменные называют верхним регистром:
<ИМЯ_КОНСТАНТЫ> = <значение>
Пример:
MAX_CONNECTIONS = 100
DEFAULT_ENCODING = "utf-8"
Объявление переменных
В Python нет ключевых слов для объявления переменных. Нет ничего вроде var, let, const, int, str и т.п.
В других языках нам пришлось бы писать сложные конструкции:
let x = 1;
int x = 1;
local x = 1
Python отказывается от такого подхода и подразумевает обычное написание названия:
x = 1
Объявление переменной происходит в момент первого присваивания:
name = "Алексей" # переменная создана
age = 30 # ещё одна
is_student = False # и ещё одна
Никаких предварительных объявлений — интерпретатор сам добавляет имя в текущее пространство имён.
<имя> = <значение>
Если присвоить значение уже существующей переменной, то значение перезапишется.
x = 1
x = 2
x = 3
print(x) # выведет 3
Пример:
counter = 0
name = "Анна"
is_valid = True
Допустимо множественное присваивание:
<имя1>, <имя2>, ..., <имяN> = <значение1>, <значение2>, ..., <значениеN>
Пример:
x, y = 10, 20
a, b, c = "A", "B", "C"
Разбор:
- справа формируется кортеж значений;
- Python выполняет распаковку по позициям;
- количество имен слева и значений справа должно совпадать.
Имеется возможность распаковки последовательности:
<первый>, *<остальные> = <последовательность>
Пример:
first, *rest = [1, 2, 3, 4]
# first = 1, rest = [2, 3, 4]
Присваивание такого характера также позволяет и обменять значения между переменными:
<имя1>, <имя2> = <имя2>, <имя1>
Пример:
a, b = b, a
Литералы
Литерал — это способ записи значения прямо в коде. Они используются при присвоении переменных:
number = 42 # 42 — целочисленный литерал
pi = 3.14159 # литерал float
text = "Hello" # строковый литерал
flag = True # булев литерал
data = [1, 2, 3] # литерал списка
config = {"db": "prod"} # литерал словаря
point = (10, 20) # литерал кортежа
binary = b"hello" # байтовый литерал
Литералы — это "сырые" значения, которые создаются как объекты в памяти, и на них можно ссылаться через переменные.
<имя> = <литерал>
Равенство значений и идентичность объекта
Как уже упоминалось, Python — динамически типизированный язык. Это означает, что тип переменной определяется во время выполнения, и она может менять тип в процессе работы программы.
value = 100 # int
value = "текст" # str
value = [1, 2] # list
Поскольку переменные — это ссылки, важно различать равенство значений (==) и идентичность объекта (is).
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True — значения одинаковые
print(a is b) # False — разные объекты в памяти
print(a is c) # True — ссылаются на один объект
Разбор:
==сравнивает содержимое коллекций.isсравнивает идентичность объектов, то есть совпадение ссылки.c = aсвязывает имена с одним объектом, поэтомуa is cдаетTrue.
Функция id() показывает уникальный идентификатор объекта:
print(id(a)) # например, 140234567890123
print(id(b)) # другой адрес
print(id(c)) # такой же, как у a
<переменная1> == <переменная2> # сравнение значений
<переменная1> is <переменная2> # сравнение объектов в памяти
Пример:
a = [1, 2]
b = [1, 2]
c = a
print(a == b) # True
print(a is b) # False
print(a is c) # True
id(<переменная>)
Пример:
x = [1, 2, 3]
print(id(x)) # уникальный адрес объекта в памяти
Области видимости
Области видимости переменных (Scopes).
Python следует правилу LEGB, определяющему, где искать переменную при обращении к ней:
- L - Local, внутри функции;
- E - Enclosing - в обрамляющих функциях (замыканиях);
- G - Global - на уровне модуля;
- B - Built-in - встроенные имена (len, print, True).
Пример:
Код ITЗагрузка примера кода…
Локальные переменные создаются внутри функции и доступны только внутри неё.
def greet():
message = "Привет!" # локальная переменная
print(message)
greet()
# print(message) # Ошибка! NameError
Глобальные переменные объявлены на уровне модуля (вне функций/классов):
counter = 0
def increment():
global counter # говорим, что хотим использовать глобальную переменную
counter += 1
increment()
print(counter) # 1
Говоря global, мы как бы говорим Python-у, что "если что, имеем в виду именно глобальную counter".
global <имя>
<имя> = <новое_значение>
Пример:
counter = 0
def increment():
global counter
counter += 1
Без global попытка изменить counter создаст локальную переменную с тем же именем.
Аналогично — nonlocal для доступа к переменным из обрамляющей области:
def outer():
x = "внешний"
def inner():
nonlocal x
x = "изменили изнутри"
inner()
print(x) # "изменили изнутри"
Разбор:
outer()создает обрамляющую область видимости.nonlocal xвнутриinner()связывает присваивание сxизouter, а не с новой локальной переменной.- после вызова
inner()значениеxвouterуже обновлено.
Поэтому в Python, как и в JavaScript, есть лексическое окружение и замыкания (closures).
Нелокальная переменная (замыкание):
nonlocal <имя>
<имя> = <новое_значение>
Пример:
def outer():
x = "внешний"
def inner():
nonlocal x
x = "изменённый"
inner()
return x
Для проверки существования глобальной переменной можно использовать условный оператор:
if '<имя>' in globals():
...
Пример:
if 'DEBUG_MODE' in globals():
print("Режим отладки активен")
Удаление переменной
Допустимо также и удаление переменной:
del <имя>
Пример:
temp = 42
del temp
# temp больше не существует в текущем пространстве имён
Замыкание
Замыкание — функция, которая запоминает значения из обрамляющей области, даже после завершения этой области.
def make_multiplier(n):
def multiplier(x):
return x * n # n — из обрамляющей области
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
Разбор:
make_multiplier(n)возвращает функциюmultiplier, которая "захватывает"n.double = make_multiplier(2)создает версию функции с фиксированным множителем 2.- при вызове
double(5)используетсяx=5и захваченноеn=2, результат10.
Здесь multiplier — замыкание, которое "захватывает" значение n. Атрибут .__closure__ позволяет посмотреть захваченные переменные:
print(double.__closure__[0].cell_contents) # 2
Это мощный механизм, используемый в декораторах, каррировании и фабриках функций. Но о функциях позже.
Правила имён переменных
Отдельной команды "объявить переменную" в Python нет: имя появляется при первом присваивании. Зато есть жёсткие правила для самого имени:
- начинается с буквы (латиница или кириллица) или с
_, но не с цифры; - дальше — только буквы, цифры и
_(пробелы и дефисы недопустимы); - регистр важен:
age,AgeиAGE— три разные переменные; - нельзя использовать ключевые слова (
if,class,forи т.д.).
user_name = "Анна" # понятное имя
total_volume = 1000 # несколько слов через _
x = 5 # короткое имя допустимо в учебных примерах
# 2name = "ошибка" # SyntaxError — имя не может начинаться с цифры
Для вывода в консоль print() часто склеивает строки через +. Для текста это удобно, для чисел + — обычное сложение; смешивать типы без преобразования нельзя:
print("Python " + "удобен") # Python удобен
print(5 + 10) # 15
# print(5 + "10") # TypeError — int + str
print("Мне " + str(10) + " лет")
Соглашения об именах
Python поддерживает соглашения об именах, влияющих на поведение. Мы уже о них говорили - это _ для временной переменной, __name__ для специальных переменных модуля.
Кроме этого, в Python нет настоящих констант, но есть соглашение - имена в верхнем регистре считаются константами:
PI = 3.14159
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
Константы не выделяются как отдельные типы данных или структуры, но если подразумевается, что будет использоваться некий литерал с постоянным значением, которое нежелательно менять, то наименование такой переменной пишется заглавными буквами:
NUMBER = 123
MAIN_NAME = "TEST"
Это не защищает от изменения, но сигнализирует, что значение не должно меняться.
PI = 3 # технически возможно, но плохо по стилю
Для настоящей защиты можно использовать модуль types.MappingProxyType (неизменяемый словарь), классы с @property или сторонние библиотеки вроде const.
from types import MappingProxyType
CONFIG = {
'API_URL': 'https://api.example.com',
'TIMEOUT': 30
}
READONLY_CONFIG = MappingProxyType(CONFIG)
# READONLY_CONFIG['API_URL'] = '...' # Ошибка!
И разумеется, нельзя использовать ключевые слова как имена:
# if = 5 # SyntaxError
# class = "A" # SyntaxError
Список ключевых слов можно получить:
import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert', ...]
В именах разрешены буквы, цифры, подчёркивание. Начинать следует с буквы или нижнего подчёркивания (_), но не с цифры. А юникод-символы лучше избегать, хотя можно.
Стиль написания в Python именно snake_case, стандарт PEP 8, а имена описывать лучше так — user_name, total_price, is_active.
Имеется множественное присваивание:
a, b = 1, 2
x, y, z = "X", "Y", "Z"
С распаковкой:
first, *rest = [1, 2, 3, 4]
# first = 1, rest = [2, 3, 4]
Обмен значениями:
a, b = b, a # без временной переменной
Удаление переменной:
x = 100
del x # имя x удаляется из пространства имён
# print(x) # NameError
del удаляет ссылку, а не объект. Объект удалится сборщиком мусора, когда на него не останется ссылок.
Проверка существования переменной:
if 'my_var' in globals():
print("Есть такая глобальная переменная")
# Или через getattr, hasattr — особенно для объектов
Пространства имён
Python использует пространства имён — словари, где ключи — имена, значения — объекты.
Виды:
- Локальное — locals()
- Глобальное — globals()
- Встроенное — dir(builtins)
x = 10
def func():
y = 20
print(locals()) # {'y': 20}
print(globals()['x']) # 10
func()
Эти функции полезны для метапрограммирования, но редко нужны в обычном коде.
locals() # локальное пространство имён
globals() # глобальное пространство имён
Пример:
value = 100
def func():
local_var = 200
print(globals()['value']) # 100
print(locals()) # {'local_var': 200}