- Глобальные, локальные и нелокальные переменные в Python
- Глобальные переменные
- Пример 1. Создаем глобальную переменную
- Локальные переменные
- Пример 2. Доступ к локальной переменной вне области видимости
- Пример 3. Создаем локальную переменную
- Глобальные и локальные переменные
- Пример 4. Локальные и глобальные переменные в одной программе
- Пример 5. Глобальная и локальная переменные с одинаковым именем
- Нелокальные переменные
- Пример 6. Создаем нелокальную переменную
- Режимы доступа public, private, protected. Сеттеры и геттеры
Глобальные, локальные и нелокальные переменные в Python
В этом руководстве вы узнаете о глобальных, локальных и нелокальных переменных в Python и о том, где и как их использовать.
Глобальные переменные
В Python переменная, объявленная вне функции или в глобальной области видимости, называется глобальной переменной. К глобальной переменной можно получить доступ как внутри, так и вне функции.
Давайте посмотрим на примере, как в Python создается глобальная переменная.
Пример 1. Создаем глобальную переменную
x = "глобальная переменная" def foo(): print("x внутри функции:", x) foo() print("x вне функции:", x)
x внутри функции: глобальная переменная x вне функции: глобальная переменная
В приведенной выше программе мы создали глобальную переменную x и задали функцию foo() , которая выводит на экран значение x . В коде программы мы вызвали функцию foo() , которая напечатала значение x внутри функции. Как вы видите, оно совпадает со значением x вне функции.
А что если нужно изменить значение x внутри функции?
x = "глобальная переменная" def foo(): x = x * 2 print(x) foo()
UnboundLocalError: local variable 'x' referenced before assignment
Python выдает ошибку, потому что он обрабатывает x как локальную переменную, но x при этом не определена внутри функции foo() .
Чтобы исправить эту ошибку, нам понадобится ключевое слово global . О том, что это такое и как оно работает, подробнее можете почитать в статье «Ключевое слово global».
Локальные переменные
Переменная, объявленная внутри тела функции или в локальной области видимости, называется локальной переменной.
Пример 2. Доступ к локальной переменной вне области видимости
def foo(): y = "локальная переменная" foo() print(y)
NameError: name 'y' is not defined
Python выдает ошибку, потому что мы пытаемся получить доступ к локальной переменной y в глобальной области видимости. Так делать нельзя: локальная переменная y «существует» только внутри функции foo() .
Давайте рассмотрим пример, который демонстрирует, как в Python создаются локальные переменные.
Пример 3. Создаем локальную переменную
Мы создаем локальные переменные, когда, например, объявляем переменные внутри функции.
def foo(): y = "локальная переменная" print(y) foo()
Теперь вернитесь к программе, в которой x была глобальной переменной, а нам нужно было изменить эту переменную внутри функции foo() .
Глобальные и локальные переменные
В этом разделе мы поговорим о том, как использовать глобальные и локальные переменные в одной программе.
Пример 4. Локальные и глобальные переменные в одной программе
x = "глобальная переменная" def foo(): global x y = "локальная переменная" x = x * 2 print(x) print(y) foo()
глобальная переменная глобальная переменная локальная переменная
В приведенном выше программе мы объявили глобальную переменную x и локальную переменную y внутри функции foo() . Затем мы использовали оператор умножения, чтобы изменить глобальную переменную x , и вывели на экран значения переменных x и y .
После вызова функции foo() значение x становится равным «глобальная переменная глобальная переменная» , потому что внутри функции строка «глобальная переменная» умножается на два. Затем функция foo() выводит на экран новое значение x и значение переменной y — «локальная переменная» .
Пример 5. Глобальная и локальная переменные с одинаковым именем
x = 5 def foo(): x = 10 print("локальная переменная x:", x) foo() print("глобальная переменная x:", x)
локальная переменная x: 10 глобальная переменная x: 5
В приведенной выше программе мы использовали одно и то же имя x как для глобальной переменной, так и для локальной переменной. Python выводит разные значения переменных x , потому что локальная переменная объявлена внутри функции `foo()`, а другая — вне ее, то есть в глобальной области видимости.
Когда мы печатаем переменную x внутри функции foo() , на экран выводится сообщение «локальная переменная x: 10» . Это называется локальной областью видимости переменной.
Когда мы печатаем переменную x за пределами foo() , на экран выводится сообщение «глобальная переменная x: 5» . Это называется глобальной областью видимости переменной.
Нелокальные переменные
Нелокальные переменные используются во вложенных функциях, локальная область видимости которых не определена. Это означает, что переменная может не находиться ни в локальной, ни в глобальной области.
Давайте на примере рассмотрим, как нелокальная переменная работает в Python.
Для этого нам понадобится ключевое слово nonlocal .
Пример 6. Создаем нелокальную переменную
def outer(): x = "локальная переменная" def inner(): nonlocal x x = "нелокальная переменная x" print("вложенная функция:", x) inner() print(":", x) outer()
вложенная функция: нелокальная переменная внешняя функция: нелокальная переменная
В приведенной выше программе есть вложенная функция inner() . Для создания нелокальной переменной мы используем ключевое слово nonlocal . Функция inner() определяется внутри функции outer() .
Примечание. Если мы изменим значение нелокальной переменной, изменится и значение локальной переменной.
СodeСhick.io — простой и эффективный способ изучения программирования.
2023 © ООО «Алгоритмы и практика»
Режимы доступа public, private, protected. Сеттеры и геттеры
На прошлых занятиях мы научились с вами создавать экземпляры классов и объявлять в них атрибуты и методы. Пришла пора сделать следующий шаг и познакомиться с механизмом ограничения доступа к данным и методам класса извне. Это основа механизма инкапсуляции.
Давайте предположим, что мы описываем класс представления точки на плоскости:
class Point: def __init__(self, x=0, y=0): self.x = x self.y = y
Когда создается экземпляр этого класса:
то имеется полный доступ ко всем его локальным атрибутам:
а, значит, их всегда можно изменить через ссылку pt:
и присвоить любые значения, в том числе и недопустимые (например, строку).
- attribute (без одного или двух подчеркиваний вначале) – публичное свойство (public);
- _attribute (с одним подчеркиванием) – режим доступа protected (служит для обращения внутри класса и во всех его дочерних классах)
- __attribute (с двумя подчеркиваниями) – режим доступа private (служит для обращения только внутри класса).
class Point: def __init__(self, x=0, y=0): self._x = x self._y = y
Так реализуется режим protected в Python. Если кто из вас программирует на других языках, например, С++ или Java, то сейчас ожидают, что мы не сможем обращаться к свойствам _x и _y через ссылку pt, так как они определены как защищенные (protected). Давайте проверим и попробуем вывести их в консоль:
Как видим, никаких ошибок не возникает и все работает так, словно это публичные свойства экземпляра класса. Но тогда зачем нам писать это нижнее подчеркивание, если оно не играет никакой роли? Одна роль у этого префикса все-таки есть: нижнее подчеркивание должно предостерегать программиста от использования этого свойства вне класса. Впоследствии это может стать причиной непредвиденных ошибок. Например, изменится версия класса и такое свойство может перестать существовать, т.к. никто не предполагал доступа к нему извне. Так что, к таким атрибутам лучше не обращаться напрямую – одно нижнее подчеркивание указывает нам, что это внутренние, служебные переменные. Давайте теперь посмотрим, как работает режим доступа private. Пропишем у локальных свойств два подчеркивания:
class Point: def __init__(self, x=0, y=0): self.__x = x self.__y = y
После запуска программы видим ошибку, что такие свойства не определены. Это говорит о том, что извне, через переменную pt мы не можем напрямую к ним обращаться. А вот внутри класса доступ к ним открыт. Пропишем метод set_coord, который будет менять локальные свойства __x и __y экземпляра класса:
def set_coord(self, x, y): self.__x = x self.__y = y
Как видите, никаких ошибок не возникает и чтобы убедиться в изменении локальных приватных свойств, определим еще один метод:
def get_сoord(self): return self.__x, self.__y
После запуска программы видим измененные координаты точки. В результате, мы с вами определили два вспомогательных метода: set_coord и get_coord, через которые предполагается работа с защищенными данными класса. Такие методы в ООП называются сеттерами и геттерами или еще интерфейсными методами. Зачем понадобилось в классах создавать приватные атрибуты да еще и определять дополнительно методы для работы с ними извне. Я об этом уже говорил на самом первом занятии по ООП, когда объяснял принцип инкапсуляции. Но, скажу еще раз. Класс в ООП следует воспринимать как некое единое целое, и чтобы случайно или намеренно не нарушить целостность работы алгоритма внутри этого класса, то следует взаимодействовать с ним только через публичные свойства и методы. В этом суть принципа инкапсуляции. Опять же, представьте автомобиль, в котором согласованно работают тысячи узлов. А управление им предполагается только через разрешенные интерфейсы: руль, коробка передач, педали газа и тормоза и т.п. Если во время движения вмешиваться напрямую в его узлы, например, будем на ходу спускать воздух из шин, то, наверное, ничего хорошего не получится. То же самое, можно сказать и о программисте, который намеренно обходит запрет и обращается к скрытым атрибутам класса напрямую, а не через сеттеры или геттеры. Так делать не нужно. Назначение интерфейсных методов не только передавать значения между приватными атрибутами класса, но и проверять их корректность. Например, в нашем случае координаты должны быть числами. Поэтому, прежде чем обновлять значения переменных, следует проверить их тип данных. Для этого можно воспользоваться функцией type и записать сеттер следующим образом:
def set_coord(self, x, y): if type(x) in (int, float) and type(y) in (int, float): self.__x = x self.__y = y else: raise ValueError("Координаты должны быть числами")
Здесь мы проверяем, что обе переданные переменные x и y должны иметь тип int или float и только после этого приватным атрибутам экземпляра класса присваиваются новые значения. Иначе, генерируется исключение ValueError. Об исключениях мы с вами еще будем говорить. Теперь, если передавать недопустимые значения координат:
то увидим ошибку ValueError. Продолжим совершенствовать наш класс Point и добавим приватный метод для проверки корректности координат. Приватный метод объявляется также как и приватная переменная – двумя подчеркиваниями и, кроме того, сделаем его методом уровня класса (о декораторе classmethod мы с вами говорили на предыдущем занятии):
@classmethod def __check_value (cls, x): return type(x) in (int, float)
def __init__(self, x=0, y=0): self.__x = self.__y = 0 if self.__check_value (x) and self.__check_value (y): self.__x = x self.__y = y def set_coord(self, x, y): if self.__check_value (x) and self.__check_value (y): self.__x = x self.__y = y else: raise ValueError("Координаты должны быть числами")
Запускаем программу и видим, что все работает. Но, при этом, доступа к этому методу извне нет, он приватный. На самом деле, в Python можно относительно легко обратиться и к приватным атрибутам извне. Если распечатать все атрибуты экземпляра:
то среди прочих мы увидим, следующие: ‘_Point__x’, ‘_Point__y’ Это и есть кодовые имена приватных атрибутов, к которым мы можем обратиться через ссылку pt:
print(pt._Point__x, pt._Point__y)
и менять их. Однако, так делать крайне не рекомендуется и двойное подчеркивание должно сигнализировать программисту, что работать с такими атрибутами нужно только через разрешенные интерфейсные методы. Иначе, возможны непредвиденные ошибки. Если у вас появилась необходимость лучше защитить методы класса от доступа извне, то это можно сделать с помощью модуля accessify. Для его установки нужно выполнить команду: pip install accessify И, затем, импортировать из него два декоратора:
from accessify import private, protected
Далее, нужный декоратор просто применяем к методу и он становится либо приватным (private), либо защищенным (protected):
@private @classmethod def check_value(cls, x): return type(x) in (int, float)