Python namespaces
Пространства имен создаются в разные моменты и имеют разное время жизни. Пространство имен, содержащее встроенные имена, создается при запуске интерпретатора Python и никогда не удаляется. Глобальное пространство имен для модуля создается при считывании определения модуля; обычно пространства имен модулей существуют до завершения работы интерпретатора. Операторы, выполняемые при вызове интерпретатора, либо считываеме из файла сценария, либо интерактивно, считаются частью модуля с именем __main__ , поэтому у них есть собственное глобальное пространство имен. Встроенные имена фактически также находятся в модуле.
Локальное пространство имен для функции создается при вызове функции и удаляется, когда функция возвращает результат или вызывает исключение, которое не обрабатывается внутри функции. Каждый рекурсивный вызов имеет свое собственное локальное пространство имен.
Область видимости (scope) — это текстовая область программы Python, где пространство имен доступно непосредственно. «Непосредственно доступный» здесь означает, что неквалифицированная ссылка на имя пытается найти имя в данном пространстве имен.
Хотя области видимости определяются статически, они используются динамически. В любой момент во время выполнения есть 3 или 4 вложенных области, чьи пространства имен доступны напрямую:
- самая внутренняя (innermost) область видимости, которая ищется первой, содержит локальные имена
- области видимости любых включающих функций (если таковые имеются), поиск которых начинается с ближайшей бласти видимости. Здесь содержатся нелокальные, но также и неглобальные имена
- предпоследняя область содержит глобальные имена текущего модуля
- самая внешняя область видимости (поиск обращается к ней в последюю очередь) — это пространство имен, содержащее встроенные имена
Если имя объявлено глобально, то тогда все ссылки и назначения переходят непосредственно в область, содержащую глобальные имена модуля. Чтобы повторно привязать переменные, которые необходимо видеть за пределами innermost области, можно использовать nonlocal ; если они не объявлены нелокальными, эти переменные доступны только для чтения (попытка записи в такую переменную просто создаст новую локальную переменную во внутренней области видимости, а внешняя переменная с таким же именем останется неизменной).
Обычно локальная область видимости ссылается на локальные имена текущей (буквальной) функции. За пределами функций локальная область видимости ссылается на то же пространство имен, что и глобальная область видимости: на пространство имен модуля. Определения классов помещают еще одно пространство имен в локальную область видимости.
Важно понимать, что области видимости определяются буквально: глобальная область видимости функции, определенной в модуле, является пространством имен этого модуля, независимо от того, откуда и под каким псевдонимом вызывается функция. С другой стороны, фактический поиск имен выполняется динамически во время выполнения — однако определение языка эволюционирует в сторону статического разрешения имен во время «компиляции», поэтому не стоит полагаться на динамическое разрешение имен. Фактически, локальные переменные уже определены статически.
Особенность Python заключается в том, что если не действует ни один global или nonlocal оператор — присвоения имен всегда попадают в самую внутреннюю область видимости. Назначения не копируют данные — они просто связывают имена с объектами. То же самое верно и для удалений: оператор del x удаляет привязку x из пространства имен, на которое ссылается локальная область видимости. Фактически, все операции, которые вводят новые имена, используют локальную область видимости: в частности, операторы импорта и определения функций связывают имя модуля или функции в локальной области.
Оператор global может использоваться, чтобы указать, что определенные переменные находятся в глобальной области видимости и должны быть там повторно привязаны.
Интерпретатор Python. В какой последовательности исполняется код. Особенности пространства имен и области видимости
Прошу прощения за нубский вопрос, но прошерстив инет не смог найти полного ответа на интерсующий вопрос. Не могу понять каким образом отрабатывает следующий пример:
Здесь код отрабатывает без ошибки. Т.к. перменной х нет в локальном пространстве имен, поэтому интерпритатор заглядывает в глобальное пространство, находит ее там и спокойно выводит на экран.
В этом же примере я получу UnboundLocalError. Ошибка возникает потому, что интерпритатор знает, что в локальном пространстве имен есть переменная x, но значение присваивается после ее вызова.
Я понимаю, что прежде чем выполнить код интерпритатор пробегает по коду и СНАЧАЛА формирует пространство имен. Т.е. перед выполнением данного кода сначала формируется пространство с переменной x и функцией fucn.
Когда функция вызывается, перед выполнением функции интерпретатор также сначала пробегает по телу функции и формирует локальное пространство имен? или же он начинает построчно выполнять тело функции?
Также прошу поправить меня, если в своих объяснениях и догадках я допустил какие-то ошибки. Очень хочу досконально разобраться в том, как ведет себя интерпретатор Python и в целом в синтаксисе и особенностях данного языка.
Пространства имен и области видимости в Python
Данная статья посвящена теме пространств имен в Python. Это структуры, которые используются для организации символических имен, присваиваемых объектам в программе.
В Python понятие объекта является ключевым. Они везде! Фактически все, что программа Python создает или с чем работает, — это объект.
Выражение присваивания создает символическое имя, которое вы можете использовать для ссылки на объект. Так выражение x = 'foo' создает символическое имя x , которое ссылается на строковый объект 'foo' .
В более сложных программах вам предстоит создавать сотни или тысячи подобных имен, указывающих на конкретные объекты. Как же Python отслеживает все эти имена и предотвращает их путаницу?
Изучив данную статью, вы узнаете:
- как Python структурирует символические имена и объекты в пространствах имен;
- когда Python создает новое пространство имен;
- как осуществляется реализация пространств имен;
- как область переменной определяет видимость символических имен.
Пространства имен в Python
Пространство имен — это совокупность определенных в настоящий момент символических имен и информации об объектах, на которые они ссылаются. Вы можете рассматривать такое пространство как словарь, в котором ключи являются именами объектов, а значения — самими объектами. Каждая пара ключ-значение соотносит имя с соответствующим ему объектом.
Пространства имен — отличная штука! Будем использовать их чаще! — Тим Петерс в “Дзен Python”.
Как утверждает Тим Петерс, пространства имен — отличная штука, которую активно использует Python. Существует 4 типа пространств имен:
- Встроенное.
- Глобальное.
- Объемлющее.
- Локальное.
Они обладают разными жизненными циклами. По мере выполнения программы Python создает необходимые пространства имен и удаляет их, когда потребность в них пропадает. Как правило, в любой момент времени существует множество пространств имен.
Встроенное пространство имен
Встроенное пространство имен содержит имена всех встроенных объектов, которые всегда доступны при работе в Python. Вы можете перечислить объекты во встроенном пространстве с помощью следующей команды:
Перечень включает, например, исключение StopIteration , такие встроенные функции, как max() и len() , а также типы объектов — int и str .
При запуске интерпретатор Python создает встроенное пространство имен. Оно сохраняется до тех пор, пока интерпретатор не завершит работу.
Глобальное пространство имен
Глобальное пространство имен содержит имена, определенные на уровне основной программы, и создаётся сразу при запуске тела этой программы. Сохраняется же оно до момента завершения работы интерпретатора.
Строго говоря, могут существовать и другие глобальные пространства имен. Интерпретатор также создает пространство данного типа для любого модуля, загружаемого программой при помощи выражения import .
Теперь, встречая понятие “глобальное пространство имен”, вы будете знать, что оно принадлежит основной программе.
Локальное и объемлющее пространства имен
Интерпретатор создает новое пространство имен при каждом выполнении функции. Это пространство является локальным для функции и сохраняется до момента завершения ее действия.
Функции не существуют независимо друг от друга только на уровне основной программы. Вы также можете определять одну функцию внутри другой.
В этом примере функция g() определена внутри тела f() . Вот что происходит в данном коде:
- Строки с 1 по 12 определяют f() , объемлющую функцию.
- Строки с 4 по 7 определяют g() , вложенную функцию.
- В строке 15 основная программа вызывает f() .
- В строке 9 f() вызывает g() .
Когда основная программа вызывает f() , Python создает для нее новое пространство имен. Аналогичным образом, когда f() вызывает g() , последняя получает свое собственное отдельное пространство. Пространство, созданное для g() , является локальным, а пространство, созданное для f() , — объемлющим.
Все эти пространства существуют до тех пор, пока выполняются соответствующие им функции. По завершении же этих функций Python может не сразу отозвать их из памяти, но при этом все ссылки на содержащиеся в них объекты сразу становятся недоступными.
Область видимости переменной
Наличие нескольких отличных пространств имен означает, что в процессе выполнения программы Python несколько разных экземпляров одного имени могут существовать одновременно. Пока каждый из них находится в собственном пространстве, все они обслуживаются по отдельности, и путаницы не происходит.
Но тут возникает вопрос. Предположим, что вы ссылаетесь на имя x в коде, а оно существует в нескольких пространствах. Как Python узнает, какое именно вы имеете в виду?
Ответ кроется в понятии области видимости имени, представляющей из себя часть программы, в которой данное имя обладает значением. Интерпретатор определяет эту область в среде выполнения, основываясь на том, где располагается определение имени и из какого места в коде на него ссылаются.
С более детальной информацией об области видимости в программировании вы можете ознакомиться на соответствующей странице Википедии.
Отвечая на заданный выше вопрос, отметим, что если ваш код ссылается на имя x , то Python будет искать его следующих областях видимости в таком порядке:
- Локальная. Если вы ссылаетесь на x внутри функции, то интерпретатор сначала ищет его в самой внутренней области, локальной для этой функции.
- Объемлющая. Если x не находится в локальной области, но появляется в функции, располагающейся внутри другой функции, то интерпретатор ищет его в области видимости объемлющей функции.
- Глобальная. Если ни один из вышеуказанных вариантов не принес результатов, то интерпретатор продолжит поиск в глобальной области видимости.
- Встроенная. Если интерпретатор не может найти x где-либо еще, то он направляет поиски во встроенную область видимости.
Эта последовательность составляет суть правила областей видимости LEGB, как его обычно называют в публикациях о Python (хотя, на самом деле, данный термин не встречается в его официальной документации). Интерпретатор начинает поиски имени изнутри, последовательно переходя от локальной области видимости к объемлющей, затем к глобальной и в завершении к встроенной.
Если интерпретатор не находит имя ни в одной из этих областей, то Python вызывает исключение NameError .
Примеры
Ниже представлен ряд примеров с правилом LEGB. В каждом из них самая внутренняя вложенная функция g() пытается вывести в консоль значение переменной с именем x . Обратите внимание, как в каждом примере происходит вывод разного значения x в зависимости от области видимости.
Пример 1. Одно определение
В этом примере имя x определено только в одной области. Оно находится за пределами функций f() и g() и поэтому относится к глобальной области видимости.
Выражение print() в строке 6 может ссылаться только на одно возможное имя x . Оно отображает объект x , определенный в глобальном пространстве имен, которым является строка 'global' .
Пример 2. Двойное определение
В следующем примере определение x появляется в двух местах: одно — вне f() и другое — внутри f() , но за пределами g() .
Как и в предыдущем примере g() ссылается на x . Но на этот раз предполагается выбор из двух определений:
- Строка 1 определяет x в глобальной области видимости.
- Строка 4 определяет x снова в объемлющей области видимости.
Согласно правилу LEGB интерпретатор находит значение в объемлющей области перед тем, как искать в глобальной. Поэтому выражение print() в строке 7 отображает 'enclosing' вместо 'global' .
Пример 3. Тройное определение
Теперь рассмотрим ситуацию, в которой x определен везде и всюду. Одно определение находится вне f() , другое — внутри f() , но за пределами g() , а третье — внутри g() .
Теперь выражение print() в строке 8 должно выбрать из трех возможных вариантов:
- Строка 1 определяет x в глобальной области видимости.
- Строка 4 определяет x в объемлющей области видимости.
- Строка 7 определяет x в третий раз в локальной области g() .
В данном случае правило LEGB утверждает, что g() сначала видит свое собственное значение x , определенное в локальной области видимости. Поэтому выражение print() отображает 'local' .
Пример 4. Отсутствие определения
В заключительном примере рассмотрим случай, в котором g() пытается вывести значение x , но x нигде не определен, поэтому мы получим ошибку.
На этот раз Python не находит x ни в одном из пространств имен, поэтому выражение print() в строке 4 выдает исключение NameError .
Словари пространств имен Python
В первом разделе мы уже рекомендовали вам рассматривать пространство имен как словарь, в котором ключи — это имена объектов, а значения — сами объекты. По сути, для глобальных и локальных пространств они именно таковыми и являются! Python действительно реализует их как словари.
Примечание. Встроенное пространство работает не как словарь. Python реализует его как модуль.
Python предоставляет встроенные функции globals() и locals() , обеспечивающие доступ к глобальным и локальным словарям пространств имен.
Функция globals()
Встроенная функция globals() возвращает ссылку на текущий словарь глобального пространства имен. Ее можно использовать для обращения к объектам в этом пространстве. Посмотрим, как это будет выглядеть при запуске основной программы.
Как видно из примера, интерпретатор уже поместил ряд записей в globals() . В зависимости от версии Python и операционной системы в вашей среде это может выглядеть несколько иначе, но в целом все равно будет похоже.
Теперь посмотрим, что происходит при определении переменной в глобальной области видимости.
Вслед за выражением присваивания x = 'foo' в словаре глобального пространства имен появляется новый элемент. Ключ словаря — это имя объекта, т. е. x , а его значение — значение объекта, а именно 'foo' .
Как правило, вы обращаетесь к этому объекту обычным способом, ссылаясь на его символическое имя x . Но можно получить к нему доступ косвенным путем посредством словаря глобального пространства имен.
Оператор проверки типов is в строке 6 подтверждает, что в действительности это один и тот же объект.
Вы можете создавать и изменять записи в глобальном пространстве имен, также используя функцию globals() .
Выражение в строке 1 равнозначно выражению y = 100 , а в строке 12 — выражению y = 3.14159 .
Этот способ создания и изменения объектов в глобальной области видимости выглядит немного оригинальным, учитывая, что с этой задачей справятся и простые выражения присваивания. Но он работает и прекрасно отражает идею.
Функция locals()
Pyhton также предоставляет соответствующую встроенную функцию locals() . Она похожа на globals() , но отличается от нее тем, что обращается к объектам в локальном пространстве имен.
Когда locals() вызывается внутри f() , она возвращает словарь, представляющий локальное пространство имен функции. Обратите внимание, что помимо локально определенной переменной s это пространство включает параметры функций x и y , поскольку они также являются локальными для f() .
Если вы вызываете locals() за пределами функции в основой программе, то она ведет себя так же как и globals() .
Между globals() и locals() существует небольшое отличие, о котором вам будет полезно узнать.
globals() возвращает актуальную ссылку на словарь, содержащий глобальное пространство имен. Это значит, что если вы вызываете globals() , сохраняете возвращаемое значение и после этого определяете дополнительные переменные, то эти новые переменные появятся в словаре, на который указывает сохраненное возвращаемое значение.
В этом примере g является ссылкой на словарь глобального пространства имен. После выражений присваивания в строках 8 и 9 x и y появляются в словаре, на который указывает g .
В свою очередь, locals() возвращает словарь, являющийся текущей копией локального пространства имен, а не ссылкой на него. Дальнейшие дополнения к локальному пространству не повлияют на предыдущее возвращаемое значение locals() до момента ее повторного вызова. Кроме того, вы не можете изменять объекты в текущем локальном пространстве имен, используя возвращаемое значение locals() .
В этом примере loc указывает на возвращаемое значение locals() , являющееся копией локального пространства. Выражение x = 20 в строке 6 добавляет x в локальное пространство, а не в копию, на которую указывает loc . Аналогично этому, выражение в строке 9 изменяет значение для ключа 's' в копии, на которую указывает loc , но это никак не влияет на значение s в текущем локальном пространстве имен.
Это едва уловимое отличие может доставить вам хлопот, если вы его не запомните.
Изменение переменных вне области видимости
Как вам известно, передача аргументов функции в Python может проходить двумя способами: по значению и по ссылке. Иногда функция может изменить свой аргумент в среде вызова, внося коррективы в соответствующий параметр, а иногда у нее такой возможности нет:
- Неизменяемый аргумент никогда не может быть изменен функцией.
- Изменяемый аргумент нельзя переопределять целиком, но зато можно изменять.
Похожая ситуация возникает, когда функция пытается изменить переменную вне своей локальной области, ведь изменение неизменяемого объекта вне ее границ ей недоступно.
Когда f() выполняет выражение присваивания x = 40 в строке 3, она создает новую локальную ссылку на объект целого числа со значением 40 . На этом этапе f() теряет ссылку на объект с именем x в глобальном пространстве имен. Таким образом, выражение присваивания не влияет на глобальный объект.
Примечание. Когда функция f() выполняет print(x) в строке 4, она отображает 40 , значение своей собственной локальной переменной x . Но после завершения действия f() значение x в глобальной области видимости по прежнему 20 .
Функция может скорректировать объект изменяемого типа, находящийся за пределами ее локальной области видимости, если изменит его внутри:
В этом случае my_list — это список, а списки являются изменяемыми типами данных. f() может вносить изменения внутрь my_list , даже если он находится вне локальной области видимости. Но если f() стремится полностью переназначить my_list , то она создаст новый локальный объект и не изменит глобальный my_list .
Это похоже на процесс, при котором f() стремится модифицировать изменяемый аргумент функции.
Объявление global
А что если вам действительно необходимо изменить значение в глобальной области видимости изнутри f() ? Python делает это возможным благодаря использованию объявления global .
Выражение global x указывает на то, что пока выполняется f() , ссылки на имя x будут вести к x , находящемуся в глобальном пространстве имен. Это значит, что присваивание x = 40 не создает новую ссылку. Вместо этого оно присваивает новое значение x в глобальной области видимости.
Как видите, globals() возвращает ссылку на словарь глобального пространства имен. При желании, вместо использования выражения global , можно было бы осуществить то же самое, применив globals() :
Но особых причин делать это таким способом у нас нет, поскольку объявление global , вероятно, точнее отражает наше намерение. Но тем не менее этот вариант позволяет продемонстрировать принцип работы globals() .
Если же имя, определенное в объявлении global , не существует в глобальной области при запуске функции, то его создаст комбинация выражений global и присваивания.
В этом примере при запуске g() в глобальной области нет объекта с именем y , но g() создаст его с помощью выражения global y в строке 8.
Вы также можете указать несколько имен, разделенных запятыми, в одном объявлении global .
Здесь x , y и z объявляются для ссылок на объекты в глобальной области видимости посредством одного выражения global в строке 4.
Имя, определенное в объявлении global , не может появиться в функции раньше выражения global .
Цель выражения global x в строке 3 состоит в том, чтобы ссылки на x вели к объекту в глобальной области видимости. Но выражение print() в строке 2 ссылается на x до объявления global , что приводит к выводу исключения SyntaxError .
Объявление nonlocal
Схожая ситуация наблюдается с определениями вложенных функций. Объявление global позволяет функции обращаться к объекту в глобальной области видимости и менять его. А что если вложенной функции необходимо изменить объект в объемлющей области? Рассмотрим пример:
В этом примере первое определение x дано в объемлющей области, а не в глобальной. Точно так же, как g() не может напрямую изменить переменную в глобальной области, она не способна изменить x в объемлющей области функции. После присваивания x = 40 в строке 5 x в объемлющей области остается 20 .
Ключевое слово global не способствует решению этой ситуации:
Поскольку x находится в объемлющей области функции, а не в глобальной, ключевое слово global здесь не сработает. После завершения действия g() значение x в объемлющей области остается 20 .
На самом деле, в этом примере выражение global x не только не предоставляет доступ к x в объемлющей области, но также создает объект с именем x в глобальной области со значением 40 .
Для модификации x в объемлющей области изнутри g() вам потребуется аналогичное ключевое слово nonlocal . Имена, определенные после nonlocal , ссылаются на переменные в ближайшей объемлющей области.
После выражения nonlocal x в строке 5, когда g() ссылается на x , оно обращается к x в ближайшей объемлющей области, чье определение дано внутри f() в строке 2.
Выражение print() в завершении f() в строке 9 подтверждает, что вызов g() изменил значение x в объемлющей области на 40 .
Лучшие практики
Несмотря на то, что Python предоставляет ключевые слова global и nonlocal , бывают ситуации, когда их использование не рекомендовано.
Когда функция меняет данные вне локальной области, используя ключевые слова ( global или nonlocal ) или напрямую преобразуя изменяемый тип внутри, то это своего рода побочный эффект, аналогичный ситуации с изменением функцией одного из своих аргументов. Частая модификация глобальных переменных обычно не приветствуется ни в Python, ни в других языках программирования.
Как и со многими другими аспектами жизни, это, скорее всего, дело вкуса. Бывают ситуации, когда разумный подход к изменению глобальных переменных помогает уменьшить сложность программы.
По крайней мере, использование ключевого слова global в Python явно свидетельствует об изменении функцией глобальной переменной. Во многих других языках функция может менять глобальную переменную только посредством присваивания, не объявляя об этом каким-либо образом. Вследствие чего становится довольно сложно отследить, где происходит изменение глобальных данных.
В общем, изменение переменных за пределами локальной области видимости обычно не требуется. Почти всегда существует лучший способ — обычно с возвращаемыми значениями функции.
Заключение
Фактически все, что программа Python использует или с чем работает, — это объект. Даже будучи короткой, она создаст много разных объектов. А в более сложной программе их количество исчисляется тысячами. Python должен отслеживать все эти объекты и их имена, и помогают ему в этом пространства имен.
Name already in use
python-docs-ru / Doc / tutorial / classes.rst
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
По сравнению с другими языками программирования, система классов в Python реализована с минимумом новой семантики и синтаксиса. Он представляет собой смесь механизмов классов найденных в C++ и Modula-3. Классы в Python предоставляют все стандартные особенности Объектно Ориентированного Программирования: механизм наследования классов разрешает множественные базовые классы, наследованный класс может переопределять любой метод своего базового класса или классов, и метод может вызывать метод базового класса с тем же именем. Объекты могут содержать произвольное количество данных любого типа. Как и модули, классы являются частью динамической природы Python: они создаются во время выполнения, и могут быть изменены после создания.
В терминологии C++, обычно члены класса (включая члены-данные) являются открытыми (public) (кроме, см. ниже :ref:`tut-private` ), а все функции-члены виртуальными. Как и в Modula-3, нет сокращений для доступа к членам объекта из его методов: функция-метод объявляется с явным первым аргументом представляющим объект, который неявно предоставляется вызовом. Как и в Smalltalk, классы сами являются объектами. Это обеспечивает семантику для импорта и переименования. В отличие от C++ и Modula-3, встроенные типы могут быть использованы в качестве базовых классов для расширения пользователем. Также, как и в C++, большинство встроенных со специальным синтаксисом (арифметические операторы, операторы индексирования и т.д.), могут быть переопределены для экземпляров класса.
(Из-за нехватки общепринятой терминологии в разговоре о классах, я буду по случаю использовать термины Smalltalk и C++. Я бы предпочёл использовать термины Modula-3, с его объектно-ориентированной семантикой, которая ближе к Python, чем C++, но я полагаю, что не многие читатели слышали о нем).
Пара слов об именах и объектах
У объектов есть индивидуальность, и множество имён (во множестве областей видимости), могут быть связаны с одним объектом. В других языках это называется псевдонимом.
Это обычно не ценят при первом знакомстве с Python, и может быть без вреда проигнорировано при работе с неизменяемыми базовыми типами (числами, строками, кортежами). Как бы то ни было, это явление может оказать неожиданное влияние на семантику кода в Питоне затрагивая изменяемые объекты, как то списки, словари и большинство других типов. Это обычно используется для пользы программы, поскольку псевдонимы, в некотором смысле, ведут себя похожим на указатели образом. Например, передача объекта дешёвая операция, поскольку передаётся только указатель согласно реализации; и если функция изменяет объект переданный как аргумент, вызывающий увидит изменения — это отменяет нужду в двух разных способах передачи параметров, как в Pascal.
Области видимости и пространства имён в Python
Перед знакомством с классами, я сначала должен рассказать вам кое-что о правилах видимости в Python. Определения классов выделывают хитрые штуки с пространствами имён, и вам нужно знать как работают области видимости и пространства имён чтобы полностью понимать что происходит. Впрочем, познания в этой теме полезны любому продвинутому программисту на Python.
Давайте начнём с некоторых определений.
Пространство имён (namespace) это сопоставление имён объектам. В настоящий момент, большинство пространств имён реализовано в виде словарей Питона, но обычно это не заметно во всех смыслах (кроме производительности), и это может поменяться в будущем. Примеры пространств имён: набор встроенных имён (включая функции, такие как :func:`abs` , и имена встроенных исключений); глобальные имена в модуле; и локальные имена при вызове функции. В каком-то смысле набор атрибутов объекта также формирует пространство имён. О пространствах имён важно знать то, что нет абсолютно никакой связи между именами в разных пространствах имён; например, два разных модуля могут определить функцию maximize без какой-либо путаницы — пользователи модулей должны указывать перед функцией имя модуля.
Кстати, Я использую термин атрибут для любого имени следующего за точкой — например, в выражении z.real , real это атрибут объекта z . Строго говоря, ссылки на имена в модуле и есть ссылки на атрибуты: в выражении modname.funcname , modname это модуль, а funcname это его атрибут. В нашем случае существует прямая связь между атрибутами модуля и глобальными именами определёнными в модуле: они разделяют общее пространство имён! [1]
Атрибуты могут быть как только для чтения так и для записи. В последнем случае, возможно присвоение атрибуту. Атрибуты модуля доступны для записи: вы можете написать modname.the_answer = 42 . Атрибуты доступные для записи могут быть удалены при помощи оператора :keyword:`del` . Например, del modname.the_answer удалит атрибут :attr:`the_answer` из объекта под именем modname .
Пространства имён создаются в разное время и имеют разную продолжительность жизни. Пространство имён содержащее встроенные имена создаётся при запуске интерпретатора Питона и никогда не удаляется. Глобальное пространство имён модуля создаётся в момент чтения определения этого модуля; обычно, пространство имён модуля «живёт» вплоть до выхода из интерпретатора.
Выражения выполненные вызовом интерпретатора верхнего уровня, будь то чтение из файла или интерактивно, рассматриваются как часть модуля названного :mod:`__main__` , так что у них есть их собственное глобальное пространство имён. (На самом деле, в модуле также находятся встроенные имена; они называются :mod:`builtins` ).
Локальное пространство имён для функции создаётся во время вызова функции, и удаляется когда происходит выход из функции или происходит исключительная ситуация которая не обрабатывается внутри функции. (На самом деле, забывается будет лучшим словом для того чтобы описать, что на самом деле происходит.) Конечно, рекурсивные вызовы все имеют свои собственные локальные пространства имён.
Область видимости (scope) это регион текста программы на Питоне где пространство имён доступно напрямую. «Доступно напрямую» здесь означает, что явная ссылка на имя приводит к попытке поиска этого имени в этом пространстве имён.
Хотя области видимости определяются статически, используются они динамически. В любое время во время исполнения, существует как минимум три вложенных пространства имён, чти пространства имён доступны напрямую:
- внутренняя область, в которой поиск выполняется в первую очередь, содержит локальные переменные
- область любых встроенных функций, которые работают с ближайшими областями, содержащими уже не локальные переменные, но и не глобальные.
- предпоследняя область содержит глобальные имена текущего модуля
- внешняя область видимости (просматриваемая последней) — это пространство имен, содержащее встроенные имена
Если имя объявлено глобально, тогда все ссылки и присвоения идут прямо в среднюю область содержащую глобальные имена модуля. Чтобы переназначить переменные найденные снаружи самой вложенной области, может быть использован оператор :keyword:`nonlocal` ; если не объявлены нелокально, эти переменные доступны только для чтения (попытка записи в такую переменную просто приведёт к созданию новой локальной переменной в самой вложенной области, оставляя одноимённую внешнюю переменную неизменной).
Обычно, локальная область ссылается на локальные имена (буквально) текущей функции. Снаружи функции, локальная область ссылается на тоже пространство имён что и глобальная область: пространство имён модуля. Определение класса помещает ещё одно пространства имён в локальной области.
Важно понимать, что области видимости определены буквально: глобальная область видимости функции которая определена в модуле и есть область видимости модуля, в не зависимости от того откуда или как вызван псевдоним функции. С другой стороны, поиск имён выполняется динамически, во время выполнения программы — однако,язык развивается в сторону статического разрешения имён, во время «компиляции», так что не опирайтесь на динамическое разрешение имён! (По сути, локальные переменные уже определены статически).
Особая причуда Python в том, что — если оператор :keyword:`global` не имеет место — присвоения всегда попадают во внутреннюю область. Присвоения не копируют данные — они просто привязывают имена объектам. Аналогично для удалений: выражение del x удаляет привязку x из пространства имён на которую ссылается локальная область видимости. Фактически, все операции которые объявляют новые имена используют локальное пространство имён: в частности, выражения :keyword:`import` и определения функций связывают имя модуля или функции в локальном пространстве имён.
Ключевое слово :keyword:`global` может быть использовано для указания того, что конкретные переменные живут в глобальном пространстве имён и должны переназначаться там; Ключевое слово :keyword:`nonlocal` указывает на то, что конкретные переменные живут в прилегающей области видимости и должны переназначаться там.
Примеры пространств имён и областей видимости
Это пример демонстрирующий как ссылаться на различные области видимости и пространства имён, и как :keyword:`global` и :keyword:`nonlocal` влияют на связывание переменных:
Этот пример выводит:
Обратите внимание как local присвоение (присвоение по умолчанию) не меняет связывание scope_test‘a со spam. :keyword:`nonlocal` присвоение изменяет связывание scope_test‘а со spam, и :keyword:`global` присвоение меняет связывание уровня модуля.
Так же вы можете видеть, что никакого предварительного связывания spam до :keyword:`global` присвоения нет.
Первый взгляд на классы
В классах мы знакомимся с небольшим количеством нового синтаксиса, тремя новыми типами объектов, и небольшим количеством новой семантики.
Синтаксис определения класса
Простейшая форма определения класса выглядит так:
Определение классов, как и определение функций ( :keyword:`def` выражения) должны быть выполнены, прежде чем они возымеют какой-то эффект. (Вы можете поместить определение класса в ветку оператора оператора :keyword:`if` , или внутри функции).
На практике, выражения внутри определения класса, это обычно определения функций, но разрешены и другие выражения (в т.ч. матерные), что бывает полезно — мы вернёмся к этому позже. Определения функций внутри класса, обычно имеют особую форму списка параметров, продиктованную соглашениями вызова методов — опять же, об этом будет рассказано позже.
При вхождении в определение класса, создаётся новое пространство имён, и используется оно как локальная область видимости — поэтому, все присвоения локальным переменным идут в этой новой области имён. В частности, определения функций связывают имена новых функций там же.
Когда происходит выход из определения класса (обычно при достижении end, a создаётся объект класса. Это в основном обёртка вокруг содержания пространства имён созданного определением класса; мы узнаем больше об объектах класса в следующем разделе. Оригинальная локальная область видимости (которая была активна непосредственно перед вхождением в объявление класса) становится активной снова, и объект класса привязывается к имени класса указанному в определении класса тут, в заголовке определения класса ( :class:`ClassName` в нашем примере).
Объекты типа класс поддерживают два вида операций: ссылки на атрибуты и инициализацию.
Ссылки на атрибуты используют стандартный синтаксис используемый для всех ссылок на атрибуты в Питоне: obj.name . Корректные имена атрибутов, это все имена, которые были в пространстве имён класса в момент когда был создан объект этого класса. Так что, если определение класса выглядит так:
тогда MyClass.i и MyClass.f корректные ссылки на атрибуты, возвращающие целое и объект функцию соответственно. Атрибутам класса так же можно присваивать, так что вы можете менять значение MyClass.i присвоением. :attr:`__doc__` так же корректный атрибут, возвращающий текст документации принадлежащий классу: «Простой пример класса» .
При создании экземпляра класса используется функциональная нотация. Просто представьте, что объект класса это функция без параметров которая возвращает новый экземпляр класса. Например, возьмём класс выше:
создаёт новый экземпляр класса и присваивает этот объект локальной переменной x .
Операция создания экземпляра класса («вызов» объекта класс), создаёт пустой объект. Многие классы создают объекты с экземплярами настроенными на определённое начальное состояние. Поэтому класс может определить специальный метод называющийся :meth:`__init__` , как-то так:
Когда класс определяет метод :meth:`__init__` , при создании экземпляра класса метод :meth:`__init__` автоматически вызывается для вновь созданного экземпляра класса. Как в этом примере, новый экземпляр класса может быть получен таким образом:
Конечно же, метод :meth:`__init__` может иметь параметры для большей гибкости. В этом случае, параметры переданные оператору создания экземпляра класса, предаются в :meth:`__init__` . Например,
Теперь, что мы можем делать с объектами экземпляров? Единственная операция доступная для объектов экземпляров, это ссылки на атрибуты. Есть два вида допустимых имён атрибутов, атрибуты данных и методы.
атрибуты данных похожи на «переменные экземпляров» в Smalltalk, и на «члены данных» в C++. Атрибуты данных не нужно объявлять; как и локальные переменные, они появляются на свет когда происходит первое присвоение. Например, если x это экземпляр :class:`MyClass` созданный выше, следующий отрывок кода выведет значение 16 , без оставления следа:
Ещё один вид ссылки атрибута экземпляра, это method. Метод это функция «принадлежащая» объекту. (В Python, термин метод применяется не только для экземпляров классов: другие типы объектов также могут иметь методы. Например, объекты список имеют методы: append, insert, remove, sort, и т.д. Однако, в дальнейшем, мы будем использовать термин метод исключительно в значении методы объекта экземпляра класса класса, если не указано иначе).
Допустимые имена методов объекта экземпляра зависят от его класса. По определению, все атрибуты класса, которые являются объектами функция определяют соответствующие методы их экземпляров. В нашем примере, x.f допустимая ссылка на метод, т.к. MyClass.f это функция, но x.i нет, т.к. MyClass.i не функция. Но x.f не то же самое, что MyClass.f — это объект метод, а не объект функция.
Обычно, метод вызывается сразу после его связи:
В примере :class:`MyClass` , это вернёт строку ‘привет, мир’ . Однако, это не обязательно вызовет метод: x.f это объект метода, и может быть сохранён и вызван позже. Например:
будет продолжать печатать привет, мир до скончания времён.
Что конкретно происходит при вызове метода? Вы могли заметить, что x.f() был вызван без параметра сверху, даже не смотря на то, что объявление для :meth:`f` указывает параметр. Что же произошло с параметром? Конечно же Python выбросит исключение когда функция требующая наличия параметра будет вызвана без всяких параметров — даже если параметр на самом деле не используется.
На самом деле вы уже могли догадаться: методы — особый случай, когда объект передаётся в качестве первого параметра функции. В нашем примере, вызов x.f() полностью идентичен MyClass.f(x) . Обычно, вызов метода со списком из n параметров эквивалентен вызову соответствующей функции со списком параметров который создан подстановкой объекта метода в качестве первого аргумента.
Если вы не понимаете как работают методы, посмотрите на реализацию и, возможно, картина прояснится. Когда происходит ссылка на экземпляр атрибута, выполняется поиск не в его данных, а в его классе. Если имя указывает на корректный атрибут класса который является объектом функция, создаётся объект метод путём упаковки (указания на) объект экземпляр и объект функция тут же находятся вместе в абстрактном объекте: это есть объект метод. Когда объект метод вызван со списком параметров, создаётся новый список параметров из экземпляра объекта и списка параметров, и объект функция вызывается с этим новым списком параметров.
Переменные экземпляра и класса
Вообще говоря, переменные экземпляра предназначены для данных уникальных для каждого экземпляра и переменные класса предназначены для атрибутов и методов общих для всех экземпляров класса:
Как обсуждалось в :ref:`tut-object` , общие данные могут иметь, возможно неожиданные эффекты когда задействованы :term:`mutable` объекты такие как списки и словари. Например, список tricks в коде ниже не должен быть использован как переменная класса потому что этот один список будет общим всеми экземплярами Dog:
Корректный дизайн этого класса должен использовать экземпляр переменной вместо этого:
Атрибуты данных переопределяют атрибуты методов с совпадающим именем; чтобы предотвратить случайные конфликты имён, которые могут привести к ошибкам которые весьма сложно найти в больших программах, мудро использовать некое соглашение, что позволит минимизировать случаи конфликтов.
Возможные соглашения включают именование методов с заглавной буквы, начинать имена атрибутов данных с короткой уникальной строки (возможно просто с подчёркивания), или используя глаголы для методов и существительные для атрибутов данных.
На атрибуты данных могут ссылаться как методы, так и обычные пользователи («клиенты») объекта. Другими словами, классы не могут быть использованы для реализации чисто абстрактный типов данных. На самом деле ничто в Питоне не делает возможным принудительное скрытие данных — это всё основывается на соглашении. (С другой стороны, реализация Python написанная на C, способна полностью скрыть детали реализации и управление объектами, если необходимо; это может быть использовано расширениями Питона написанными на C).
Клиентам следует использовать атрибуты данных с осторожностью — clients may mess up invariants maintained by the methods by stamping on their data attributes. Обратите внимание, что клиенты могут добавлять свои собственные атрибуты данных экземпляру объекта не затрагивая корректность методов, до тех пор пока не произошло конфликта имён — опять же, соглашение об именовании может уберечь вас от множества проблем.
Не существует быстрого способа ссылаться на атрибуты данных (или другие методы!) внутри методов. Я нахожу, что это повышает читаемость методов: нет шанса перепутать локальные переменные и переменные экземпляра во время чтения кода метода.
Часто, первый параметр метода называется self . Это ничто иное как соглашение: имя self не имеет никакого особого значения для Python. Обратите внимание, однако, если не следовать этому соглашению ваш код может стать менее читаемым для других программистов на Python, и что так же очевидно, что class browser может рассчитывать на такое соглашение.
Любой объект функция, который является атрибутом класса определяет метод для экземпляра этого класса. Не обязательно, чтобы определение функции фактически находилось внутри определения класса: присвоение объекта функция локальной переменной в классе вполне допустимо. Например:
Теперь f , g и h все являются атрибутами класса :class:`C` которые ссылаются на объекты функция, и следовательно они все — методы экземпляров класса :class:`C` — h абсолютно идентичен g . обратите внимание, что такая практика обычно только запутывает читающего программу.
Методы могут вызывать другие методы используя атрибуты параметра self :
Методы могут ссылаться на глобальные переменные тем же образом, каким и на обычные функции. Глобальная область видимости ассоциированная с методом является модулем содержащим его определение. (Класс никогда не использует глобальное пространство имён). Пока кто-нибудь не столкнётся с редкой необходимостью использовать глобальные данные в методе, существует множество законных пользователей глобального пространства имён: как минимум, функции и модули импортированные в глобальное пространство имён могут использоваться методами, так же как и функциями и классами определёнными в них. Обычно, класс содержит метод, который сам определён в глобальном пространстве имён, и в следующем разделе мы узнаем веские причины того, почему методу может понадобиться ссылаться на собственный класс.
Каждое значение это объект, и поэтому имеет класс (также называемый его типом). Он хранится в object.__class__ .
Конечно же, возможность языка не стоило бы называть «класс» без поддержки наследования. Синтаксис определения наследованного класса выглядит примерно так:
Имя класса :class:`ИмяБазовогоКласса` должно быть определено в области видимости содержащей определение наследуемого класса. Вместо имени базового класса, так же допустимы другие произвольные выражения. Это может оказаться полезным, например, например когда базовый класс определён в другом модуле:
Выполнение определения наследуемого класса происходит так же как и для базового класса. Когда объект класса построен, базовый класс запоминается. Это используется для разрешения ссылок на атрибуты: если запрашиваемый атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно если базовый класс сам по себе наследован от какого-то иного класса.
Нет ничего особенного в создании экземпляра наследованных классов: ИмяНаследованногоКласса() создаёт новый экземпляр класса. Ссылки на методы разрешаются следующим образом: ищется соответствующий атрибут класса, спускаясь по цепи базовых классов, если необходимо, и ссылка на метод корректна если это приводит к объекту функция.
Наследованные классы могут переназначать методы своих базовых классов. Потому что методы не имеют специальных привилегий когда вызывают другие методы того же объекта, метод базового класса который вызывает другой метод определённый в том же базовом классе может привести к вызову метода наследованного класса который переназначает его. (Для C++ программистов: все методы в Питоне эффективно виртуальные ).
Переназначение метода в наследованном классе может по факту расширять нежели просто заменять метод базового класса с тем же именем. Существует простой способ вызова метода базового класса напрямую: просто вызывая ИмяБазовогоКласса.имяметода(self, параметры) . Это так же иногда полезно и для клиентов. (Обратите внимание что это работает только если базовый класс доступен как ИмяБазовогоКласса в глобальной области видимости).
Python имеет две встроенные функции которые работают с наследованием:
- Используйте :func:`isinstance` чтобы проверить тип экземпляра: isinstance(obj, int) будет True только если obj.__class__ является :class:`int` или каким-либо классом наследованным от :class:`int` .
- Используйте :func:`issubclass` чтобы проверить экземпляр класса: issubclass(bool, int) выдаст True т.к. :class:`bool` является подклассом :class:`int` . Однако, issubclass(float, int) выдаст False т.к. :class:`float` не является подклассом :class:`int` .
Python так же поддерживает форму множественного наследования. Определение класса с несколькими базовыми классами выглядит так:
Для большинства применений, в простейших случаях, вам можете подумать о поиске атрибутов наследованных от родительского класса как нижний первый, слева-направо, не искать дважды в одном и том же классе где нет перехлёста в иерархии. Увы, если атрибут не найден в классе :class:`ИмяНаследованногоКласса` , оно искало в классе :class:`Базовый1` , затем (рекурсивно) в базовых классах :class:`Базовый1` , и если не найдено там, оно искало в :class:`Базовый2` , и т.д.
По факту, этот процесс несколько сложнее; порядок разрешения метода изменяется динамически чтобы поддерживать кооперативные вызовы :func:`super` . Этот подход в некоторых языках со множественным наследованием известен как вызов-следующего-метода (call-next-method) и является более мощным, чем супер вызов, который можно найти в языках с одиночным наследованием.
Динамический порядок необходим потому что все случаи множественного наследования демонстрируют один или более ромбовидных отношений (где хотя бы к одному из родительских классов доступ может быть получен несколькими путями из самого нижнего класса). Например, все классы наследуются от :class:`object` , так что в любом случае множественное наследование предоставляет более одного пути чтобы достигнуть :class:`object` . Чтобы не допустить доступа к базовым классам более одного раза, динамический алгоритм линеаризует порядок поиска так чтобы сохранить порядок слева-направо указанный в каждом классе, который вызывает каждого родителя только один раз, и это монотонно (это означает, что класс может быть наследован не затрагивая порядок в котором расположены его родители). Собранные вместе, эти свойства делают возможным разработку надёжных и расширяемых классов с множественным наследованием. За подробной справкой, обращайтесь https://www.python.org/download/releases/2.3/mro/.
«Приватные» переменные экземпляра те к которым нет доступа ниоткуда, кроме как изнутри объекта — не существуют в Питоне. Однако, есть соглашение которому следует большинство кода на Питоне: имя начинающееся со знака подчёркивания (например, _spam ) следует воспринимать как не-публичную часть API (будь то функция, метод или член данных). Это следует воспринимать как особенности реализации и то, что может измениться без предупреждения.
Поскольку существует корректный способ применения приватных переменных класса (например, для предотвращения конфликта имён с именами определёнными в подклассах), существует ограниченная поддержка такого механизма как :dfn:`name mangling` . Любой идентификатор вида __spam (как минимум два символа подчёркивания и хотя бы один такой символ в конце) буквально заменяется на _classname__spam , где classname это текущее имя класса из имени которого убран предшествующий знак(и) подчёркивания. This mangling сделано без учёта синтаксического положения этого идентификатора, до тех пор пока он встречается в определении класса.
Name mangling чтобы позволять подклассам переопределять методы без нарушения интраклассового вызова методов. Например:
Note that the mangling rules are designed mostly to avoid accidents; it still is possible to access or modify a variable that is considered private. This can even be useful in special circumstances, such as in the debugger.
Notice that code passed to exec() or eval() does not consider the classname of the invoking class to be the current class; this is similar to the effect of the global statement, the effect of which is likewise restricted to code that is byte-compiled together. The same restriction applies to getattr() , setattr() and delattr() , as well as when referencing __dict__ directly.
Иногда бывает полезно иметь тип данных похожий на «record» в Pascal или «struct» в C, собрать вместе несколько именованных элементов данных. Пустой класс прекрасно подойдёт:
Куску кода на Питоне который ожидает особый абстрактный тип данных часто может быть передан класс который эмулирует методы этого типа данных. Допустим, если у вас есть функция которая форматирует некие данные из объекта файл, вы можете определить класс с методами :meth:`read` и :meth:`!readline` который берёт данные из стокового буфера, и передаёт их в качестве параметра.
Методы объекта экземпляра тоже имеют атрибуты: m.__self__ это объект экземпляра с методом :meth:`m` , а m.__func__ это объект функция соответствующая этому методу.
Исключения это тоже классы
Определённые пользователем исключения тоже представляют из себя классы. Используя этот механизм возможно создать расширяемые иерархии исключений.
Вот две новые (семантические) формы выражения :keyword:`raise` :
В первой форме, Class должен быть экземпляром :class:`type` или класса наследованного от него. Первая форма — краткая запись для:
Класс в блоке :keyword:`except` совместим с исключением если тот же класс или его базовый класс (но тут нет другого пути — блок except в котором перечислен наледованный класс несовместим с базовым классом). Например, следующий код выведет B, C, D в указанном порядке:
Обратите внимание, что если перевернуть (поставить except B первым), код напечатает B, B, B — первый подходящий блок except clause сработает.
Когда выводится сообщение об ошибке о необработанном исключении, выводится имя класса исключения, зачем двоеточие и пробел, и наконец экземпляр преобразованный в строку используя встроенную функцию :func:`str` .
К данному моменту вы, возможно, успели заметить что большинство объектов контейнеров могут быть пройдены в цийве используя оператор :keyword:`for` :
Этот способ доступа ясен, понятен и удобен. Использование итераторов пронизывает и унифицирует Python. Внутри, оператор :keyword:`for` вызывает :func:`iter` на объекте контейнера. Эта функция возвращает объект итератор который определяет метод :meth:`
iterator.__next__` который получает доступ к элементам в контейнере по одному раз раз. Когда больше не остаётся элементов, :meth:`
iterator.__next__` выбрасывает исключение :exc:`StopIteration` которое заставляет цикл оператора :keyword:`for` прекратиться. Вы можете вызвать метод :meth:`
iterator.__next__` используя встроенную функцию :func:`next` ; в этом примере показано как всё это работает:
Теперь, когда вы увидели механику стоящую за протоколом итератора, очень легко добавить поведение итератора вашим классам. Определите метод :meth:`__iter__` который возвращает объект с методом :meth:`
iterator.__next__` . Если этот класс определяет :meth:`__next__` , то :meth:`__iter__` может просто возващать self :
:term:`Generator` ы простой но мощный инструмент для создания итераторов. Они пишутся как обычные функции но используют оператор :keyword:`yield` там где возвращают данные. Каждый раз когда функция :func:`next` вызывается на них, генератор продолжает работу с того места где остановился (он запоминает все данные и какой оператор был выполнен последним). Пример показывает насколько просто и легко могут быть созданы такие генераторы:
Всё, что может быть сделано при помощи генераторов, так же может быть сделано с помощью основанных на классах итераторах как описано в предыдущем разделе. Что делает генераторы такими компактными, это то, что методы :meth:`__iter__` и :meth:`
Другая ключевая особенность это то, что локальные переменные и состояние выполнения автоматически сохраняются между вызовами. По этой причине функции становится легче писать и они получаются намного чище, чем при использовании переменных экземпляра вроде self.index и self.data .
В дополнение к автоматически создающимся методам и сохранению состояния программы, когда генераторы уничтожаются, они автоматически выбрасывают :exc:`StopIteration` . В купе, эти возможности облегчают создание итераторов требующих больше усилий, чем обычные функции.
Некоторые простые генераторы могут быть написаны кратко как выражения используя синтаксис похожий на вписанные списки, но со круглыми скобками вместо квадратных. Эти выражения разработаны для ситуаций, когда генератор используется прямо в прилегающей функции. Выражения генераторов более компактны, но менее универсальны чем полноценные описания генераторов и чаще менее требовательны к памяти чем эквивалентные вписанные списки.