вторник, 8 декабря 2015 г.

Области видимости функций

Локальное пространство имен функций; Локальные переменные функций.


Для того, чтобы продолжить изучение разработки и применения функций, нам необходимо четко представлять, как взаимодействуют между собой переменные, которые создаются в разных местах нашего кода, так называемые области видимости функций.

Начнем с объявления переменных внутри функций и с объявления переменных для передачи аргументов внутри функции:
>>> def myfunc(var1, var2): # var1 и var2 - переменные для передачи аргументов в функцию
...     result = var1 + var2 # result - переменная объявленная внутри функции
...     return result
...
>>>

Все три переменные: var1, var2, result - переменные, которые мы объявили внутри функции и они называются локальными переменными этой функции. Также говорят, что эти переменные находятся в локальном пространстве имен этой функции. Мы можем утверждать следующее:
Переменные объявленные таким образом, будут существовать только во время работы функции;
После того, как функция прекратит свою работу, эти переменные будут удалены и при следующем вызове этой функции, они опять будут созданы;
Имена локальных переменных функции, никак не конфликтуют с именами переменных в других областях видимости.
Касательно жизни локальных переменных, тут все достаточно просто. Обращения к таким переменным, возможны только внутри блока кода функции. Наглядно увидеть то, что локальные переменные создаются при вызове функции и удаляются после завершения ее работы, вы не сможете, а значит просто принимаем это, как аксиому.
Резюмируем: Переменные которые будут объявлены в круглых скобочках для получения аргументов и переменные которые будут объявлены внутри функции при помощи оператора присваивания "=", создаются во время вызова функции и использовать их может только эта функция:
>>> def myfunc(a,b): # a, b - Локальные переменные
...     c = a + b # c - локальная переменная
...     return c
...
>>> a
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'a' is not defined
>>> b
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'b' is not defined
>>> c
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'c' is not defined
>>> myfunc(5,3)
8
>>> a
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'a' is not defined
>>> b
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'b' is not defined
>>> c
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'c' is not defined
>>>

Тут видно, что ни после объявления функции с переменными a, b, c, ни после использования этой функции, когда a, b, c были задействованы во время работы функции, они не оставили после себя никакого следа. Этот пример будет более очевидным после того, как мы поговорим о глобальной области видимости.
Также я говорил, что имена локальных переменных не конфликтуют с именами переменных в других областях видимости. Про это говорим далее.

Глобальное пространство имен


Глобальное пространство имен - это первый уровень того пространства, где мы пишем наш код, Т.Е. если вы объявляете переменные в интерактивном режиме вне функций - это будет глобальное пространство имен интерактивного режима. Мы еще вернемся к вопросу глобального пространства имен во время разговора о модулях. Также это работает для кода, который вы пишете в файле:
>>> a = 5 # Глобальная переменная
>>> b = "Hello" # Глобальная переменная
>>> print a
5
>>> print b
Hello
>>>

Еще один пример:
>>> word = "Hello" # word - Глобальная переменная
>>> if len(word) < 10:
...     s = "Short word" # s - глобальная переменная
...
>>> print s
Short word
>>>

Когда я говорил, что локальные переменные функций и глобальные переменные не конфликтуют между собой, я имел ввиду то, что если локальная переменная и глобальная переменная будут иметь одинаковое имя - это никак не повлияет на их независимость и в процессе выполнения функции, которая будет содержать объявленную локальную переменную с таким же именем, какое у нас будет встречаться в глобальной области видимости, локальная и глобальная переменная никак не повлияют друг на друга:
>>> a = 5 # a - Глобальная переменная
>>> def myfunc(a): # a - Локальная переменная внутри функции myfunc
...     print a
...
>>> myfunc(20) # Передаем аргумент "20" в локальную переменную аргумента "a", в функции myfunc
20
>>> print a # глобальная переменная a, никак не изменилась
5
>>> myfunc("Hello")
Hello
>>> a
5
>>>

Функция "myfunc", принимает один переданный в нее аргумент в переменную "a". Переменная "a", в нашей функции локальная, Т.К. Мы объявили ее в нашей функции, как переменную для аргумента. Функция просто печатает на экран содержимое своей локальной переменной "a", в которую мы, при вызове функции, передаем какое-то значение. Также у нас есть глобальная переменная "a", которой мы присвоили значение "5". В процессе использования функции "myfunc", локальная переменная "a" этой функции, никак не изменяет глобальную переменную "a". Глобальная переменная "a", также никак не влияет на локальную переменную "a" нашей функции.
Еще один пример:
>>> a = 1 # a - Глобальная переменная
>>> b = 1 # b - Глобальная переменная
>>> c = 1 # c - Глобальная переменная
>>> a
1
>>> c
1
>>> b
1
>>> def myfunc(a): # a - локальная переменная
...     b = 5 # b - Локальная переменная
...     c = a + b # c - Локальная переменная
...     return c
...
>>> a
1
>>> b
1
>>> c
1
>>> myfunc(10)
15
>>> a
1
>>> b
1
>>> c
1
>>>

Мы объявили 3 глобальных переменных: a, b, c, значение которых установили в 1. Функция принимает один аргумент и помещает ее в локальную переменную "a", внутри объявляет локальную переменную "b" и присваивает ей значение "5", также внутри себя объявляет локальную переменную "c" и помещает в нее результат сложения (a + b) и возвращает нам значение переменной "c". Как видно, никакой из этапов объявления или использования нашей функции, не затронул глобальные переменные, равно, как и глобальные переменные не затронули работу локальных переменных функции.

Чтоже произойдет, если мы объявим глобальную переменную "a". Определим какую-то функцию, где не станем объявлять локальную переменную "a", но попробуем обратиться к ней?
>>> a = 5 # a - Глобальная переменная
>>> def myfunc():
...     print a # Обращение к переменной "a", которую мы не объявляли в нашей функции
...
>>> myfunc()
5
>>> a = 20
>>> myfunc()
20
>>> a = "Hello"
>>> myfunc()
Hello
>>>

Тут сработал закон поиска имен в областях видимости, который мы рассмотрим чуть ниже. Что мы можем утверждать?
Если функция обращается к какой-то переменной, но мы не объявляли эту переменную в локальной области видимости нашей функции, она ищет эту переменную в области видимости, в которой мы создавали нашу функцию, Т.Е. в глобальной области видимости и получает ее значение .
У нас была глобальная переменная "a" и мы обращались к переменной "a", внутри нашей функции. Поскольку мы не объявляли переменную "a", в нашей функции, она стала искать переменную "a", выше, нашла ее в глобальном пространстве имен, получила значение глобальной переменной "a" и воспользовалась им, Т.Е. Отпечатала его на экран.
Давайте посмотрим на менее очевидный пример того, как нельзя обращаться к глобальной переменной из функции:
>>> a = 5
>>> def myfunc():
...     print a
...     a = "Hello"
...     print a
...
>>> myfunc()
Traceback (most recent call last):
  File "", line 1, in
  File "", line 2, in myfunc
UnboundLocalError: local variable 'a' referenced before assignment
>>>

Что происходит? У нас есть глобальная переменная "a". Внутри нашей функции, мы печатаем "a" на экран, потом явно объявляем локальную переменную "a" и еще раз выводим ее на экран. Вродебы все должно работать, Т.Е. поначалу функция должна была бы вывести нам на экран "5", а потом уже слово "Hello", которое мы присвоили после первого "print", объявив переменную "a", локальной. Насамом деле функция - это тоже объект, конструкция которого создается целиком в момент объявления функции. И если в каком-то месте блока кода нашей функции мы явно объявим переменную локальной, она будет локальной уже на момент вызова функции и функция не будет искать эту переменную в другой области видимости. Наш первый принт уже обращался к локальной переменной "a", которая была объявлена локальной для нашей функции, но на момент вызова первого "print", не имела никакого значения, Т.К. Значение мы ей присвоили уже в следующей строке.
Резюмируем: Если в какой-то части нашей функции, мы объявляем какую-то переменную локальной, это значит, что в любом месте блока кода нашей функции, при попытке обратиться к этой переменной, мы будем обращаться к локальной переменной. Акцентируйте ваше внимание на том, что функции достаточно увидеть конструкцию "a =", чтобы объявить "a", в локальном пространстве имен. Естественно, что если мы попробуем получить значение этой переменной в нашей функции до того, как мы присвоим ей какое-то значение, мы получим трекбек.

Правило поиска имен "LEGB"


Частенько в разнообразной литературе, мы можем встречать понятие поиска имен "LEGB", что это значит эта аббревиатура? Пространства имен делятся на следующие слои:
Local - Локальное пространство имен функций;
Enveloping - Пространство имен объемлющей функции (Если мы объявим функцию "a", в нее вложим объявление функции "b". Локальное пространство имен функции "a", Будет пространством имен объемлющей функции для функции "b", примерно также, как глобальное пространство имен - это объемлющее пространство имен для функции "a");
Global - Глобальное пространство имен;
Builtins - пространство имен встроенных в Python функций.
Само же правило говорит о том, что функция при обращении к какой-то переменной, для начала будет искать ее в своем локальном пространстве имен, затем в пространстве объемлющей функции, если таковая есть, после в глобальном пространстве имен и, в самом конце, будет искать необходимое имя в пространстве "Builtins", где располагаются встроенные в Python функции.

Пространство "Enveloping" - объемлющей функции


Давайте посмотрим на следующий пример:
>>> a = 20 # Глобальная переменная
>>> def Gfunc():
...     a = 10 # Локальная переменная
...     def Ifunc(): # Вложенная функция в функцию Gfunc
...             print a
...     Ifunc() # В конце функции Gfunc, вызываем функцию Ifunc
...
>>> Gfunc()
10
>>>

Здесь мы создали глобальную переменную "a" со значением 20. Объявили функцию Gfunc, в рамках функции, объявили локальную переменную "a" со значением 10. Далее мы объявили функцию Ifunc, которая получилась вложенной в функцию Gfunc. В функции Ifunc, мы обратились к переменной "a", которую мы не объявляли в рамках этой функции. Когда функция Ifunc не нашла такую переменную в своей локальной области видимости, она стала искать ее в следующем(внешнем) пространстве имен. По нашему правилу "LEGB" - это пространство объемлющей (Enveloping) функции, в данном случае, функции Gfunc, где была объявлена переменная "a" со значением 10. Найдя эту переменную в объемлющем пространстве, Ifunc вывела нам значение этой переменной на экран. Если бы в объемлющем пространстве функции Gfunc, мы не объявили бы переменную "a", функция Ifunc, не найдя эту переменную в объемлющем пространстве, пошла бы искать ее в глобальной области видимости и наткнулась бы на глобальную переменную "a" со значением 20.

Область видимости "builtins"


Builtins - это самая внешняя область видимости, где содержатся встроенные функции Python. Давайте посмотрим на следующее:
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>>

Функция dir(), вызванная без аргумента, вернет нам находящиеся в глобальной области видимости функции и переменные. В данном случае, мы получили системные функции.
>>> MyVar = 1
>>> def MyFunc():
...     return 0
...
>>> dir()
['MyFunc', 'MyVar', '__builtins__', '__doc__', '__name__', '__package__']
>>>

После того, как мы объявили переменную и функцию в глобальной области видимости, мы смогли увидить их в глобальной области видимости, вызвав функцию dir() без аргументов. Где же прячутся встроенные функции Python? В действительности, они находятся в модуле "__builtins__", который является самой внешней(объемлющей) областью видимости в нашем правиле "LEGB". Чтобы убедиться в этом, необходимо вызвать функцию dir(), передав в качестве аргумента, имя этого модуля:
dir(__builtins__)
Список функций по этому запросу вырезан из соображений экономии места.
Мы можем объявить переменную в области видимости builtins, но этого никогда не стоит делать в прикладном программировании! Для этого есть целый ряд причин, который станет очевиден в дальнейших уроках.
>>> __builtins__.MyVar = 5 # MyVar - переменная в области видимости builtins
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> # В глобальной области видимости нет переменной MyVar
...
>>> MyVar # Но если мы обратимся к переменной MyVar, она будет найдена в области видимости builtins
5
>>>




Комментариев нет:

Отправить комментарий