'''Тема урока: локальные и глобальные переменные
Локальные переменные
Область действия локальной переменной
Глобальные переменные
Глобальные константы
Решение задач
Аннотация. Локальные и глобальные переменные.

Локальные переменные
Локальными называются переменные, объявленные внутри функции и доступные только ей самой. Программный код за
пределами функции к ним доступа не имеет.

Рассмотрим функцию print_texas(), которая выводит информацию о количестве птиц, обитающих в Техасе.

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')
В теле функции мы создаем переменную birds, которой присваивается значение, равное 5000. Такая переменная является
локальной для функции print_texas(). Всякий раз, когда переменной внутри функции присваивается значение, в результате
 создается локальная переменная. Она принадлежит функции, в которой создается,  и к ней получает доступ только
 программный код этой функции.

Термин “локальная” указывает на то, что переменная может использоваться лишь в этом месте — внутри функции, в
которой создается.

Если программный код одной функции попытается обратиться к локальной переменной, принадлежащей другой функции,
произойдет ошибка.

Рассмотрим следующий программный код:

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')

def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')
Функция print_california() обращается к локальной переменной birds функции print_texas(). Вызов функции
print_california(), приводит к ошибке:

NameError: name 'birds' is not defined
Локальные переменные скрыты от других функций, поэтому другие функции могут иметь собственные локальные переменные
с тем же именем. Например,

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')

def print_california():
    birds = 9000
    print('В Калифорнии обитает', birds, 'птиц.')
В каждой из этих двух функций есть локальная переменная с именем birds. Но они никогда не видны одновременно, так как
 находятся в разных функциях.

Когда выполняется функция print_texas(), видима переменная birds, значение которой равно 5000. Когда выполняется
функция print_california(), видима переменная birds, значение которой равно 9000.

Разные функции могут иметь локальные переменные с одинаковыми именами, потому что они не видят локальных переменных
друг друга.

Область действия переменной
Область действия переменной – часть программы, в которой можно к ней обращаться, та функция, где она создана.
Переменная видима только программному коду в области ее действия. Никакая инструкция за пределами функции не может
обращаться к такой переменной.

К локальной переменной не может обращаться программный код, который появляется внутри функции до того, как переменная
 была создана.

Например, если в функции print_texas() поменять местами строки кода:

def print_texas():
    print('В Техасе обитает', birds, 'птиц.')
    birds = 5000
то при вызове этой функции получим ошибку:

UnboundLocalError: local variable 'birds' referenced before assignment
Ошибка возникла в результате преждевременного обращения к еще не объявленной локальной переменной birds.

Область действия параметрической переменной
Область действия параметрической переменной — функция, в которой этот параметр используется. К параметрической
переменной имеет доступ весь программный код этой функции.

Рассмотрим уже известную нам функцию:

def draw_box(height, width):
    for i in range(height):
        print('*' * width)
Параметрические переменные тут height, width. Внутри функции объявляется одна локальная переменная i.

Примечания
Примечание 1. Параметрическая переменная тоже локальная.

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

# def print_paris():
#     print(s)
#     s = 'I love Paris'
#
# print_paris()

# def print_paris():
#     s = 'I love Paris'
#     print(s)
#
# def print_london():
#     s = 'I love London'
#     print(s)
#
# s = 'I love Moscow'
# print_paris()
# print_london()
# print(s)

# def swap(a, b):
#     a, b = b, a
#
# a = 4
# b = 3
# swap(a, b)
# print(a - b)



'''Глобальные переменные
Глобальными называются переменные, объявленные в основной программе и доступные как программе, так и всем ее функциям.

Рассмотрим следующий программный код:

birds = 5000    # глобальная переменная

def print_texas():
    print('В Техасе обитает', birds, 'птиц.')

def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')
В самом начале программы создаем глобальную переменную birds, значение которой равно 5000. Далее описываем две 
функции, обращающиеся к глобальной переменной. Результатом выполнения следующего кода:

print_texas()
print_california()
будет:

В Техасе обитает 5000 птиц.
В Калифорнии обитает 5000 птиц.
Обмен информацией между основной программой и функциями осуществляется только с помощью параметров функций и 
глобальных переменных.

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

Рассмотрим следующий программный код:

birds = 5000   # глобальная переменная

def print_texas():
    birds = 1000  # локальная переменная
    print('В Техасе обитает', birds, 'птиц.')

def print_california():
    birds = 7000  # локальная переменная
    print('В Калифорнии обитает', birds, 'птиц.')
В самом начале программы мы создаем глобальную переменную birds, значение которой равно 5000. Далее мы описываем две 
функции, в которых создаются локальные переменные с таким же именем birds. Таким образом при обращении к переменной 
birds внутри функций будет происходить обращение именно к локальной переменной.

Результатом выполнения следующего кода:

print_texas()
print_california()
будет:

В Техасе обитает 1000 птиц.
В Калифорнии обитает 7000 птиц.
Глобальные переменные – зло
Большинство программистов согласны, что следует ограничить использование глобальных переменных либо не использовать
 их вообще. Причины следующие.

Глобальные переменные затрудняют отладку программы. Значение глобальной переменной может быть изменено любой 
инструкцией в программном файле. Если обнаружится, что в глобальной переменной хранится неверное значение, то 
придется отыскать все инструкции, которые к ней обращаются, чтобы определить, откуда поступает плохое значение.
 В программе с тысячами строк кода сделать это непросто.
Функции, использующие глобальные переменные, обычно зависят от этих переменных. Если возникнет необходимость применить 
такую функцию в другой программе, скорее всего придется эту функцию перепроектировать, чтобы она не опиралась на 
глобальную переменную.
Глобальные переменные затрудняют понимание программы. Глобальная переменная может быть модифицирована любой 
инструкцией в программе. При необходимости разобраться в какой-то части программы, использующей глобальную 
переменную, придется узнать обо всех других частях программы, обращающихся к этой глобальной переменной.
В большинстве случаев следует создавать переменные локально и передавать их в качестве аргументов в функции, которым
 нужно к ним обратиться.

Глобальные константы
Хотя следует избегать использования глобальных переменных, в программе допускается применение глобальных констант. 
Глобальная константа – глобальное имя, ссылающееся на неизменное значение. Поскольку значение глобальной константы 
не может быть изменено во время исполнения программы, можно не беспокоиться о потенциальных опасностях, обычно 
связанных с использованием глобальных переменных.

Несмотря на то, что язык Python не позволяет создавать настоящие глобальные константы, их можно имитировать при
 помощи глобальных переменных. Если глобальная переменная не объявляется с использованием ключевого слова global
  внутри функции, то присвоенное ей значение невозможно изменить внутри этой функции. 

Математический модуль math  определяет две глобальные переменные, math.pi и math.e, которым присвоены математические 
значения констант π = 3.14159265 и e = 2.71828.

Ключевое слово global
Если нужно, чтобы инструкция внутри функции присваивала значение глобальной переменной, то требуется дополнительный 
шаг. В этом случае, глобальная переменная должна быть объявлена внутри функции.

Рассмотрим следующий программный код:

def print_texas():
    global birds
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')

def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')

print_texas()
print_california()
 Результатом выполнения следующего кода:

print_texas()
print_california()
будет:

В Техасе обитает 5000 птиц.
В Калифорнии обитает 5000 птиц.'''
x = 5

def add():
    global x
    x = 3
    x = x + 5
    print(x)

add()
print(x)