C глобальные переменные: Глобальные переменные | Программирование на C и C++

Содержание

1.3.0 Локальные и глобальные переменные · Python Express Course

Подробнее о namespace читайте в следующем разделе. Здесь будет короткое описание основных моментов.

Область видимости (пространство имен) — область, где хранятся переменные. Здесь определяются переменные и делают поиск имен.

Операция = связывает имена с областью видимости (пространством имен)

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

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

Создаются переменные присвоением =

Обычно пишут программу так:

a = 1           

def f():
    print(a)    

f()             

Этот код будет работать так же:

def f():
    print(a)    
a = 1           
f()             

Напечатает 1. Глобальная переменная а сначала была создана, а потом была вызвана функция f(). В функции f() видна глобальная переменная a.

Особенность интерптерируемого языка. Сначала — это раньше в процессе выполнения, а не «на строке с меньшим номером».

Локальная переменная создается внутри функции или блока (например, if или while). Локальная переменная видна только внутри того блока (функции), где была создана.

def f():
    a = 1   
f()
print(a)    

Ошибка «builtins.NameError: name ‘a’ is not defined»

  • Локальные переменные создаются =.
  • Каждый вызов функции создает локальную переменную (свою, новую) (каждый вызов функции создает свой новый namespace)
  • после завершения функции ее локальные переменные уничтожаются.
  • аргументы функции тоже являются локальными переменными (при вызове функции идет = параметру значения).

Итого: Если в функции было =, то мы создали локальную переменную. Если = не было, то читаем глобальную переменную.

Можно создавать в разных функциях локальные переменные с одинаковыми именами. В функциях foo и bar создали переменные с одинаковыми именами а.

Можно (но не надо так делать!) создавать локальную переменную с тем же именем, что и глобальную. pylint поможет найти такие переменные.

def f():
    a = 1               
    print(a, end=' ')   
a = 0                   
f()
print(a)                

Напечатает 1 0.

  1. создается глобальная переменная а = 0
  2. вызывается f()
  3. в f создается локальная переменная а = 1 (теперь нельзя доступиться из функции f к глобальной переменной a)
  4. в f печатается локальная переменная a = 1
  5. завершается f
  6. печатается глобальная переменная а = 0

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

def f():
    print(a)    
    if False:
        a = 0   
a = 1           
f()

global говорит, что переменная относится к глобальному namespace. (В этот момент переменная НЕ создается). Переменную можно создать позже.

def f():
    global a
    a = 1
    print(a, end=' ')
a = 0
f()
print(a)

выведет «1 1», т.к. значение глобальной переменной будет изменено внутри функции.

Рекурсивный вызов функции

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

Например, n! = n * (n-1)!, 0! = 1. Запишем это математическое определение факториала в виде кода.

def fact(n):
    if n == 0:
        return 1
    return n * fact(n-1)

print(fact(5))

При вызове fact(5) создается namespace c n=5, далее идет вызов f(4) и создается еще один namespace, в нем n=4 (это другая переменная n, она в другом пространстве имен и та n=5 из этого пространства не доступна).

Вложенные области видимости

Можно определять одну функцию внутри другой.

Чтение переменной внутри функции. Ищем имя:

  • в локальной области видимости функции;
  • в локальных областях видимости объемлющих функций изнутри наружу;
  • в глобальной области видимости модуля;
  • в builtins (встроенная область видимости).

x = value внутри функции:

  • создает или изменяет имя х в текущей локальной области видимости функции;
  • если был unlocal x, то = создает или изменяет имя в ближайшей области видимости объемлющей функции.
  • если был global x, то = создает или изменяет имя в области видимости объемлющего модуля.
X = 99              
def f1():
    X = 88          
    def f2():
        print(X)    
    f2()
f1()                
f2()                

В f2() нельзя изменить значение Х, принадлежащей функции f1(). Вместо этого будет создана еще одна локальная переменная, но уже в пространстве имен функции f2(). Напечатает 77 88:

X = 99              
def f1():
    X = 88          
    def f2():
        X = 77      
        print(X)    
    f2()
    print(X)        
f1()

Если нужно изменять значение переменной Х, которая принадлежит пространству имен объемлющей (enclosed) функции, то добавляют unlocal

X = 99              
def f1():
    X = 88          
    def f2():
        unlocal X   
        X = 77      
        print(X)    
    f2()
    print(X)        
f1()

При определении, к какому namespace относится имя, используют правило LEGB:

  • Когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается отыскать его в четырех областях видимости – в локальной (local, L), затем в локальной области любой объемлющей инструк- ции def (enclosing, E) или в выражении lambda, затем в глобальной (global, G) и, наконец, во встроенной (built-in, B).
    • Поиск завершается, как только будет найдено первое подходящее имя.
    • Если требуемое имя не будет найдено, интерпретатор выведет сообщение об ошибке.

Локальные и глобальные переменные — Функции и рекурсия

[МУЗЫКА] [МУЗЫКА] Мы совсем немного упоминали локальные переменные и параметры функции, а теперь давайте разберемся подробнее, что же это такое. Итак, сделаем очень простую функцию, которая печатает число, хранящееся в переменной a. Такое бессмысленное название, конечно, это непростительно, что я называю функцию f, а переменную a, но давайте, в этот раз можно. Итак, мы в основной программе кладем в переменную a значение 1, а в функции печатаем это значение. Давайте посмотрим, будет ли это работать. Запускаем, и да, напечаталась наша единица. Пока что все переменные, которые мы использовали, были глобальные. Что значит глобальные? Это значит, они видны из любого места программы, из основного кода и абсолютно из любой функции.

Вот здесь наша переменная a глобальная, и когда мы печатаем какое-то значение функции, то печатаем значение этой глобальной переменной a. Что можно еще попробовать? Давайте попробуем сделать наоборот. В функции положить что-то в переменную a, вызвать эту функцию из основной программы и затем напечатать значение переменной a. И давайте запустим. Смотрите: она подчеркнула красным, значит, скорее всего, у нас ничего работать не будет. И действительно не работает. Она говорит: name a is not defined, то есть не определено значение, имя переменной a. Почему так произошло? Потому что функции, все переменные, значения которых вы изменяете, считаются локальными, то есть находящимися внутри этой функции. Правило простое: если не меняете значение, то переменная считается глобальной. Как только вы меняете какое-то значение, переменная считается локальной. Локальной — это значит, видна только внутри функции. И менять ее, обращаться к ней вы можете только внутри функции. Как только экземпляр функции закончился, переменная исчезает.
И естественно, из основной программы вы доступ к этой переменной получить не можете. Давайте посмотрим еще разные интересные моменты. Итак, допустим, напечатаем это значение a функции, а в основной программе сделаем глобальную теперь переменную a со значением 0. И посмотрим, что будет происходить. Итак, запустим нашу программу. Напечаталось 1 и 0. Итак, мы положили в глобальную переменную a значение 0. Затем функция. Мы создали переменную, сделали a = 1. Как я уже говорил, присваивание значит — переменная локальная. То есть у нас внутри функции создалась переменная с тем же именем a, как и глобальная переменная. И мы напечатали собственно локальную переменную. Как только мы в функции создаем переменную с тем же названием, что и глобальная, мы уже к глобальной переменной с таким именем доступ получить даже на посмотреть не можем. Всё. То есть это два абсолютно разных a: a глобальное и a локальное. В основной программе используется, естественно, глобальная переменная a, а внутри функции — локальная переменная, поскольку ей было присваивание.
И они друг на друга никак не влияют, что мы и увидели. Посмотрим: а, может быть, сработает вот такая штука. Попробуем напечатать переменную a в функции. Если мы так оставим, то это будет глобальная переменная. Давайте, кстати, проверим. Да, действительно, все окей, ничего не сломалось, напечаталось значение глобальной переменной a, посмотреть можно. А теперь сделаем какой-нибудь невероятный if. if false — if, который не выполняется никогда. И в этом if поменяем значение переменной. Вот нам уже подчеркнула красным, значит, все будет плохо, давайте убедимся. Действительно, все плохо. Она говорит, что к переменной a локальной доступ осуществляется перед присваиванием. То есть там еще ничего не лежит, а мы уже смотрим. С этой ошибкой мы могли уже столкнуться, если пытались использовать переменную до того, как мы ее инициализировали, положили что-то. То есть даже если этот if не выполняется никогда, если в теле функции, в командах, которые там выполняются, есть хоть одно присваивание, то переменная уже считается локальной.
Всё. Таким образом, это правило работает не только для живого кода, но и для мертвого кода, который никогда не выполнится, оно тоже работает. Это делается заранее и без анализа, без выполнения программы. Хорошо, таким образом мы поняли, по какому признаку переменная внутри функции локальная, а по какому — глобальная. Но что же делать, если очень хочется изменить глобальную переменную из функции? Конечно, это желание странное. Вообще говоря, когда функции работают с глобальными переменными, это очень плохо. Почему? Потому что какой-то человек может взять ваш код, например, вы ее выложили в свободный доступ, и ему понравилась какая-то функция, которая делает что-то полезное. Он не хочет брать ваш код целиком, он хочет взять одну конкретную функцию. Берет ее, вставляет ее в свой код, и она не работает. Почему? Потому что вы использовали какие-то глобальные переменные, которых, естественно, у этого человека в коде нет. Понятно, что ему нужно разбираться, лезть куда-то, смотреть в вашем коде, что за глобальная переменная.
Скорее всего, он просто плюнет на ваш код и напишет сам или возьмет чей-то чужой. И это будет довольно плохо. Таким образом, надо по возможности избегать использования вообще даже на посмотреть глобальных переменных внутри функции, и все, что нужно функции для работы, передавать в качестве параметра. Но, тем не менее, если вы вдруг будете читать чей-то чужой код, нужно понимать, что происходит. Итак, чтобы иметь возможность менять значения глобальных переменных внутри функции, нужно написать слово global, а затем перечислить все глобальные переменные через запятую, которые вы хотите поменять. Если бы у нас их было две, мы могли бы написать вот так. Но у нас одна, поэтому мы пишем всего одну переменную. И давайте посмотрим. Сначала напечатаем текущее значение, а потом изменим его. И должно вывестись 0 и 1. Сначала старое значение, которое у нас определено в основной программе, затем оно печатается внутри функции, и затем глобальное значение меняется, потому что теперь мы можем это делать. Поехали. Да, действительно, напечатался сначала 0, а затем напечаталась единица. То есть таким образом можно менять значения глобальных переменных из функции, но, опять же, еще раз напоминаю: по возможности все, что нужно для функции, передавать ей в качестве параметра. В принципе, вы можете, если что-то забыли, ничего страшного, можете ставить эксперименты. Среда программирования вам сразу подчеркнет какие-то неправильные вещи. Ну а если вы хотите более подробную информацию, то можете навести курсор, либо попытаться запустить вашу программу и почитать, в какой строке ошибка, и более какое-то подробное описание. Но и эксперименты такого рода, хотя правило простое, вы, опять же, можете ставить, если что-то забыли. Вспомните, что что-то там было про локальные и глобальные переменные. И разобраться, я думаю, вы сможете, довольно просто. Ну и опять же, у вас среда программирования подчеркивает разные нехорошие ситуации. Здесь аж целых две не очень хорошие ситуации: это не ошибки, но странности. Первая из них говорит о том, что у вас локальная переменная называется так же, как глобальная. И второе сообщение о том, что значение этой переменной сейчас никак не используется. Эти подсказки помогут вам разобраться, что происходит, и если происходит что-то неожиданное, не то, чего вы ожидаете, то, возможно, увидев, что у вас подчеркнуто такой не очень заметной серой линией, и наведя курсор туда, вы сможете прочитать что же там происходит, и это вас может навести на мысль. Например, вы хотели использовать глобальную переменную a, напечатать. А у вас подчеркивает серым. Или допустим, получили неожиданный эффект: ожидали увидеть нолик, а увидели единицу. И тогда смотрите, в чем у вас была ошибка, понимаете и исправляете. Например, вы хотели сделать что-то совершенно другое и случайно назвали переменную так же. Теперь мы назвали переменную по-другому, и никаких подчеркиваний у нас нет. Значит, все будет выполняться ровно так, как мы ожидаем. То есть при разработке среды программирования, старались учесть многочисленные стандартные ошибки, которые возникают у многих людей, и как-то попытаться помочь вам их исправить. Следуйте рекомендациям, и все будет получаться хорошо. [МУЗЫКА] [МУЗЫКА]

Глобальные переменные GlobalVariables — Стандартные функции

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

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

Функция GlobalVariablesDeleteAll()

 int GlobalVariablesDeleteAll(string prefix_name=NULL)

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

Параметры:

prefix_name — префикс имени удаляемых глобальных переменных.

Пример простого скрипта deleteall.mq4, удаляющего все без исключения глобальные переменные клиентского терминала.


int start()
{
GlobalVariablesDeleteAll();
PlaySound("W2.wav");
return;
}

Скрипт можно применять только в том случае, если в клиентском терминале нет ни одной исполняющейся программы, использующей GV-переменные. В противном случае исполнение скрипта может нарушить логику вычислений в других программах, что может привести к неконтролируемым действиям. После исполнения скрипта окно глобальных переменных клиентского терминала (F3) будет пустым:


Рис. 153. Вид окна глобальных переменных клиентского терминала после исполнения скрипта deleteall.mq4.


Функции для работы с глобальными переменными


Функция Краткое описание
GlobalVariableCheck Возвращает значение TRUE, если GV-переменная существует, иначе возвращает FALSE
GlobalVariableDel Удаляет глобальную переменную. При успешном удалении функция возвращает TRUE, иначе FALSE.
GlobalVariableGet Возвращает значение существующей глобальной переменной или 0 в случае ошибки.
GlobalVariableName Функция возвращает имя глобальной переменной по порядковому номеру в списке глобальных переменных.
GlobalVariableSet Устанавливает новое значение глобальной переменной. Если переменная не существует, то система создает новую глобальную переменную. При успешном выполнении функция возвращает время последнего доступа, иначе 0.
GlobalVariableSetOnCondition Устанавливает новое значение существующей глобальной переменной, если текущее значение переменной равно значению третьего параметра check_value. Если переменной не существует, функция сгенерирует ошибку ERR_GLOBAL_VARIABLE_NOT_FOUND (4058) и вернет FALSE. При успешном выполнении функция возвращает TRUE, иначе FALSE.
GlobalVariablesDeleteAll Удаляет глобальные переменные. Если префикс для имени не задан, то удаляются все глобальные переменные. В противном случае удаляются только те переменные, имена которых начинаются на указанный префикс. Функция возвращает количество удаленных переменных.
GlobalVariablesTotal Функция возвращает общее количество глобальных переменных.

Для получения подробного описания этих и других функций необходимо обратиться к справочной документации на MQL4.community, сайте MetaQuotes Ltd. или к разделу «Справка» в редакторе MetaEditor.


как использовать на примерах global

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

С другой стороны, переменная, объявленная внутри определенного блока кода, будет видна только внутри этого же блока — она называется локальной.

Разберемся с этими понятиями на примере.

def sum():  
    a = 10   # локальные переменные 
    b = 20  
    c = a + b  
    print("Сумма:", c)  
  
sum() 

Вывод: Сумма: 30.

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

Для решения этой проблемы используются глобальные переменные.

Теперь взгляните на этот пример с глобальными переменными:

a = 20  # определены вне функции
b = 10  


def sum():  
    c = a + b  # Использование глобальных переменных  
    print("Сумма:", c)  

  
def sub():  
    d = a - b  # Использование глобальных переменных 
    print("Разница:", d)  
  
  
sum()
sub()

Вывод:

Сумма: 30
Разница: 10

В этом коде были объявлены две глобальные переменные: a и b. Они используются внутри функций sum() и sub(). Обе возвращают результат при вызове.

Если определить локальную переменную с тем же именем, то приоритет будет у нее. Посмотрите, как в функции msg это реализовано.

def msg():  
    m = "Привет, как дела?"  
    print(m)  
  
msg()  
m = "Отлично!"  # глобальная переменная
print(m) 

Вывод:

Привет, как дела?
Отлично!

Здесь была объявлена локальная переменная с таким же именем, как и у глобальной. Сперва выводится значение локальной, а после этого — глобальной.

Ключевое слово global

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

Правила использования global

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

Пример без использования глобального ключевого слова.

c = 10  


def mul():
    c = c * 10 
    print(c) 


mul() 

Вывод:

line 5, in mul
    c = c * 10
UnboundLocalError: local variable 'c' referenced before assignment

Этот код вернул ошибку, потому что была предпринята попытка присвоить значение глобальной переменной. Изменять значение можно только с помощью ключевого слова global.

c = 10  


def mul():
    global c
    c = c * 10 
    print("Значение в функции:", c)  

mul()  
print("Значение вне функции:", c)  

Вывод:

Значение в функции: 100
Значение вне функции: 100

Здесь переменная c была объявлена в функции mul() с помощью ключевого слова global. Ее значение умножается на 10 и становится равным 100. В процессе работы программы можно увидеть, что изменение значения внутри функции отражается на глобальном значении переменной.

Глобальные переменные в модулях Python

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

1. Создаем файл name.py для хранения глобальных переменных:

a = 10 
b = 20 
msg = "Hello World"

2. Создаем файл change.py для изменения переменных:

import name

name.a = 15  
name.b = 25
name.msg = "Dood bye"  

Меняем значения a, b и msg. Эти переменные были объявлены внутри name, и для их изменения модуль нужно было импортировать.

3. В третий файл выводим значения измененных глобальных переменных.

import name
import change

print(name.a) 
print(name.b)
print(name.msg)

Значение изменилось. Вывод:

15
25
Dood bye

Global во вложенных функциях

Можно использовать ключевое слово global во вложенных функциях.

def add():
    a = 15
  
    def modify():
        global a
        a = 20

    print("Перед изменением:", a)
    print("Внесение изменений")
    modify()
    print("После изменения:", a)


add()
print("Значение a:", a)  

Вывод:

Перед изменением: 15
Внесение изменений
После изменения: 15
Значение a: 20

В этом коде значение внутри add() принимает значение локальной переменной x = 15. В modify() оно переназначается и принимает значение 20 благодаря global. Это и отображается в переменной функции add().

Часто задаваемые вопросы | Python 3 для начинающих и чайников

Некоторые не совсем очевидные вещи, с которыми сталкиваются начинающие программисты Python.

Почему я получаю исключение UnboundLocalError, хотя переменная имеет значение?

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

Этот код:

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
10

работает, но следующий код:

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

приводит к UnboundLocalError:

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

Это происходит потому, что, когда вы делаете присваивание переменной в области видимости, она становится локальной в этой области и скрывает другие переменные с таким же именем во внешних областях.

Когда последняя инструкция в foo присваивает новое значение переменной x, компилятор решает, что это локальная переменная. Следовательно, когда более ранний print пытается напечатать неинициализированную переменную, возникает ошибка.

В примере выше можно получить доступ к переменной, объявив её глобальной:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
>>> foobar()
10

Это явное объявление требуется для того, чтобы напомнить вам, что (в отличие от внешне аналогичной ситуации с переменными класса и экземпляра), вы на самом деле, изменяете значение переменной во внешней области видимости:

>>> print(x)
11

Вы можете сделать подобную вещь во вложенной области видимости использованием ключевого слова nonlocal:

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
>>> foo()
10
11

Каковы правила для глобальных и локальных переменных в Python?

В Python, переменные, на которые только ссылаются внутри функции, считаются глобальными. Если переменной присваивается новое значение где-либо в теле функции, считается, что она локальная, и, если вам нужно, то нужно явно указывать её глобальной.

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

Почему анонимные функции (lambda), определенные в цикле с разными значениями, возвращают один и тот же результат?

Например, вы написали следующий код:

>>> squares = []
>>> for x in range(5):
...    squares.append(lambda: x**2)

Это даёт вам список из 5 функций, считающих x**2. Можно ожидать, что, будучи вызванными, они вернут, соответственно, 0, 1, 4, 9, и 16. Однако, вы увидите, что все они возвращают 16:

>>> squares[2]()
16
>>> squares[4]()
16

Это случается, поскольку x не является локальной для lambda, а определена во внешней области видимости, и получается тогда, когда она вызывается — а не когда определяется.

В конце цикла, x=4, поэтому все функции возвращают 4**2, то есть 16. Это можно также проверить, изменив значение x и посмотрев на результат:

>>> x = 8
>>> squares[2]()
64

Чтобы избежать подобного, необходимо сохранять значения переменных локально:

>>> squares = []
>>> for x in range(5):
...    squares.append(lambda n=x: n**2)

Здесь, n=x создаёт локальную для функции переменную n и вычисляется в момент определения функции:

>>> squares[2]()
4
>>> squares[4]()
16

Это применимо не только к анонимным, а также и к обычным функциям.

Как организовать совместный доступ к глобальным переменным для нескольких модулей?

Канонический способ организовать подобный доступ — это создать отдельный модуль (часто называемый config или cfg). Просто добавьте import config в каждый модуль приложения. При этом модуль становится доступен через глобальное имя. Поскольку существует только один экземпляр модуля, любые изменения, произведённые в модуле отражаются везде. Например:

config.py:

x = 0

mod.py:

import config
config.x = 1

main.py:

import config
import mod
print(config.x)

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

Как правильнее использовать импортирование?

В общих случаях не используйте from modulename import *. Это засоряет пространство имён того, кто импортирует. Некоторые люди избегают этой идиомы даже для тех немногих модулей, которые были спроектированны, чтобы так импортироваться. Это такие модули как Tkinter и threading.

Импортируйте модули в начале файла. Это отвечает на вопрос, какие модули требует Ваш код и находится ли имя модуля в области видимости. Запись по одному импорту на строку упрощает добавление и удаление операторов импорта, но множественный импорт будет занимать меньше места на экране.

Хорошая практика, если Вы импортируете модули в следующем порядке:

  • стандартные библиотечные модули (например, sys, os, getopt, re)
  • модули сторонних разработчиков (всё, что установлено в директории site-packages) — например, PIL, NumPy и т. д.
  • локально созданные модули

Иногда бывает необходимо поместить импорт в функцию или класс, чтобы избежать проблем с циклическим импортом. Gordon McMillan советует:

Циклический импорт отлично работает, если оба модуля используют форму import <module>. Но они терпят неудачу, когда второй модуль хочет извлечь имя из первого (from module import name) и импорт находится на внешнем уровне. Это происходит из-за того, что имена первого модуля ещё недоступны, так как первый модуль занят импортом второго.

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

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

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

Эта техника полезна, если многие из импортов не являются необходимыми, и зависят от того, как программа будет исполняться. Вы также можете поместить импорт в функцию, если конкретные модули используются только в этой функции. Обратите внимание, что загрузить модуль в первый раз может оказаться дорого из-за задержки на инициализацию модуля, однако повторные загрузки «бесплатны», они стоят только пары поисков в словарях. Даже если имя модуля исчезло из области видимости, модуль скорее всего до сих пор находится в sys.modules.

Почему значения по умолчанию разделяются между объектами?

Этот тип ошибки часто встречается среди начинающих. Предположим, функция:

def foo(mydict={}):  # Опасность: разделяемая ссылка между вызовами
    ... compute something . ..
    mydict[key] = value
    return mydict

В первый раз, когда вы вызываете функцию, mydict содержит одно значение. Второй раз, mydict содержит 2 элемента, поскольку, когда foo() начинает выполняться, mydict уже содержит элемент.

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

По определению, неизменяемые объекты (числа, строки, кортежи и None), безопасны при изменении. Изменение изменяемых объектов, таких как словари, списки, и экземпляры пользовательских классов может привести к неожиданным последствиям.

Поэтому, хорошей практикой является не использовать изменяемые объекты в качестве значений по умолчанию. Вместо этого, используйте None и внутри функции, проверяйте аргумент на None и создавайте новый список/словарь. Например, не пишите:

def foo(mydict={}):
    ...

Но пишите так:

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

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

def expensive(arg1, arg2, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Расчёт значения
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result     # Кладём результат в кэш
    return result

Как передать опциональные или именованные параметры из одной функции в другую?

Получить такие параметры можно с помощью спецификаторов * и ** в списке аргументов функции; они возвращают кортеж позиционных аргументов и словарь именованых параметров. После этого Вы можете передать их в другую функцию, используя в её вызове * и **:

def f(x, *args, **kwargs):
    . ..
    kwargs['width'] = '14.3c'
    ...
    g(x, *args, **kwargs)

Почему изменение списка ‘y’ изменяет также список ‘x’?

Если вы написали код:

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

вы, возможно, будете удивлены тому, что добавление в y изменяет также и x.

Два факта приводят к такому результату:

  • Переменные — это просто ссылки на объекты. y = x не создаёт копию списка — это просто создаёт переменную y, которая ссылается на тот же объект, что и x.
  • Списки изменяемы.

После вызова append, содержимое объекта было изменено с [] на [10]. Поскольку x и y ссылаются на один и тот же объект, использование любого из них даёт нам [10].

Если мы используем неизменяемые объекты:

>>> x = 5  # числа неизменяемы
>>> y = x
>>> x = x + 1  # 5 нельзя изменить. Мы создаём НОВЫЙ объект
>>> x
6
>>> y
5

мы можем видеть, что x и y больше не равны, поскольку числа неизменяемы, и x = x + 1 не изменяет число 5 путем увеличения. Вместо этого, создаётся новый объект 6 и присваивается переменной x (то есть, изменяется то, на какой объект ссылается x). После этого у нас 2 объекта (6 и 5) и 2 переменные, которые на них ссылаются.

Некоторые операции (например y.append(10) и y.sort()) изменяют объект, в то время, как внешне похожие операции (например y = y + [10] и sorted(y)) создают новый объект. Вообще в Python (и во всех случаях в стандартной библиотеке), метод, который изменяет объект, возвращает None, чтобы помочь избежать ошибок. Поэтому, если вы написали

y = y.sort()

думая, что это даст вам отсортированную копию y, вы вместо этого получите None, что скорее всего приведёт к легко диагностируемой ошибке.

Однако, существует один класс операций, где одна и та же операция ведёт себя по-разному с различными типами: расширенные операторы присваивания. Например, += изменяет списки, но не кортежи или числа (a_list += [1, 2, 3] эквивалентно a_list.extend([1, 2, 3])) и изменяет список, в то время, как some_tuple += (1, 2, 3) и some_int += 1 создают новый объект.

Если вы хотите знать, ссылаются ли 2 переменные на один объект или нет, вы можете использовать оператор is, или встроенную функцию id.

Как создавать функции более высокого порядка?

Есть два пути: использовать вложенные функции или вызываемые объекты. Например, с использованием вложенных функций:

def linear(a, b):
    def result(x):
        return a * x + b
    return result

Использование вызываемого объекта:

class linear:

    def __init__(self, a, b):
        self.a, self.b = a, b

    def __call__(self, x):
        return self.a * x + self.b

В обоих случаях,

taxes = linear(0.3, 2)

даёт функцию, что (к примеру) taxes(10e6) == 0.3 * 10e6 + 2.

Использование вызываемого объекта — немного медленнее, и в результате получается больше кода. Однако, заметьте, что несколько функций могут разделять свою сигнатуру с помощью наследования:

class exponential(linear):
    # __init__ наследуется
    def __call__(self, x):
        return self. a * (x ** self.b)

Объект может сохранять свое состояние для нескольких вызовов:

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1

count = counter()
inc, dec, reset = count.up, count.down, count.set

Здесь inc, dec, reset выступают в роли функций, которые разделяют одну и ту же переменную.

Как скопировать объект в Python?

В общем случае, с помощью модуля copy.

Некоторые объекты можно скопировать более просто. Словари имеют метод copy:

newdict = olddict.copy()

Последовательности могут быть скопированы путём срезов:

new_l = l[:]

Как узнать доступные методы и атрибуты объекта?

dir(x) возвращает список методов и атрибутов.

Как можно узнать имя объекта?

Вообще говоря, никак, поскольку объекты в действительности не имеют имён. Важно: присваивание всегда связывает имя с объектом. Это верно и для инструкций def и class.

>>> class A:
...    pass
...
>>> B = A
>>>
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x7fbcc3ee5160>
>>> print(a)
<__main__.A object at 0x7fbcc3ee5160>

Возможно, класс имеет имя: однако, хотя он связан с двумя именами и запрашивается через имя B, созданный экземпляр всё ещё считается экземпляром класса A. Однако, невозможно сказать, имя экземпляра a или b, поскольку оба они связаны с одним и тем же значением.

Какой приоритет у оператора «запятая»?

Запятая не является оператором в Python.

>>> "a" in "b", "a"
(False, 'a')

Поскольку запятая — не оператор, но разделитель между выражениями, пример выше исполняется как если бы было введено:

("a" in "b"), "a"

А не

"a" in ("b", "a")

То же самое верно и для операторов присваивания (=, += и другие). Они не являются операторами как таковыми, а лишь синтаксическими разделителями в операциях присваивания.

Есть ли в Python эквивалент тернарного оператора «?:» в C?

Да. Синтаксис:

[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y

Можно ли писать обфусцированные однострочники?

Можно.

from functools import reduce

# Простые числа < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))

# Первые 10 чисел Фибоначчи
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))

# Множество Мандельброта
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2. 1, 0.7, -1.2, 1.2, 30, 80, 24))
#    \___ ___/  \___ ___/  |   |   |__ lines on screen
#        V          V      |   |______ columns on screen
#        |          |      |__________ maximum of "iterations"
#        |          |_________________ range on y axis
#        |____________________________ range on x axis

Не пытайтесь это делать дома!

Почему -22 // 10 равно -3?

Поскольку i % j имеет тот же знак, что j. А ещё

i == (i // j) * j + (i % j)

Как можно изменить строку?

Никак, поскольку строки неизменяемы. В большинстве ситуаций, нужно просто сделать новую строку из различных частей. Однако, если так нужно, можно использовать io.StringIO, либо модуль array:

>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'

>>> import array
>>> a = array. array('u', s)
>>> print(a)
array('u', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('u', 'yello, world')
>>> a.tounicode()
'yello, world'

Как использовать строки для вызова функций/методов?

Существует несколько приёмов.

  • Лучший — использование словаря, ставящего соответствие строке функцию. Его главное достоинство — строки не обязаны совпадать с названиями функций.
def a():
    pass

def b():
    pass

dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs

dispatch[get_input()]()
  • Использование встроенной функции getattr:
import foo
getattr(foo, 'bar')()
  • Использование locals или eval (не рекомендуется)
def myFunc():
    print("hello")

fname = "myFunc"

f = locals()[fname]
f()

f = eval(fname)
f()

Как удалить все символы новой строки в конце строки?

Можно использовать S.rstrip(«\r\n») для удаления символов новой строки, без удаления конечных пробелов:

>>> lines = ("line 1 \r\n"
. ..          "\r\n"
...          "\r\n")
>>> lines.rstrip("\r\n")
'line 1 '

Как создать многомерный список?

Возможно, вы попробуете этот неудачный вариант:

>>> A = [[None] * 2] * 3

Это выглядит правильно, если напечатать:

>>> A
[[None, None], [None, None], [None, None]]

Но если вы присвоите значение, то оно появится в нескольких местах:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

Причина в том, что оператор * не создаёт копию, а только ссылку на существующий объект. *3 создаёт список из 3 ссылок на один и тот же список. Изменение в одной строке изменяют другие, что, вероятно, не то, что вы хотите.

Возможные пути решения:

A = [None] * 3
for i in range(3):
    A[i] = [None] * 2
w, h = 2, 3
A = [[None] * w for i in range(h)]

Или, можно использовать специальные модули, предоставляющие матрицы. Наиболее известным является NumPy.

Почему a_tuple[i] += [‘item’] не работает, а добавление работает?

Это из-за того, что расширенный оператор присваивания — оператор присваивания, а также из-за разницы между изменяемыми и неизменяемыми объектами в Python.

Это обсуждение относится в общем, когда расширенные операторы присваивания применяются к элементам кортежа, которые указывают на изменяемые объекты, но мы будем использовать список и +=, как образец.

Если вы напишете:

>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Причина исключения должна быть понятна: 1 добавляется к объекту a_tuple[0], но когда мы пытаемся присвоить результат, 2, к первому элементу в кортеже, мы получаем ошибку, поскольку мы не можем изменить элемент кортежа.

То есть, это выражение делает следующее:

>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
 ...
TypeError: 'tuple' object does not support item assignment

Когда мы пишем что-то вроде:

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
 . ..
TypeError: 'tuple' object does not support item assignment

Исключение немного более неожиданное, но более удивителен тот факт, что, несмотря на ошибку, элемент добавился!

>>> a_tuple[0]
['foo', 'item']

Чтобы понять, что случилось, нужно знать, что:

  • Если объект определяет метод __iadd__, он вызывается, когда выполняется +=, и возвращенное значение используется для присваивания
  • Для списков, __iadd__ эквивалентен вызову extend для списка

Таким образом,

>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]

Эквивалентен:

>>> result = a_list.__iadd__([1])
>>> a_list = result

Таким образом, наш пример с кортежом эквивалентен:

>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

__iadd__ завершился успешно, и список увеличился, но присваивание законилось ошибкой.

Переменные

6. Переменные

Следующий базовый строительный блок, с которым нам нужно ознакомиться, — переменные. Common Lisp поддерживает два вида переменных: лексические и динамические1). Эти два типа переменных примерно соответствуют «локальным» и «глобальным» переменным других языков. Однако, это соответствие лишь приблизительное. С одной стороны «локальные» переменные некоторых языков в действительности гораздо ближе к динамическим переменным Common Lisp 2). И, с другой, локальные переменные некоторых других языков имеют лексическую область видимости не предоставляя всех возможностей, предоставляемых лексическими переменными Common Lisp. В частности, не все языки, предоставляющие переменные, имеющие лексическую область видимости, поддерживают замыкания.

Чтобы сделать все еще более запутанным, многие формы, которые работают с переменными, могут использоваться как с лексическими, так и с динамическими переменными. Поэтому я начну с обсуждения некоторых аспектов переменных Lisp, которые применимы к обоим видам переменных, а затем рассмотрю специфические характеристики лексических и динамических переменных. Далее я обсужу оператор присваивания общего назначения Common Lisp — SETF, который используется для присваивания новых значений переменным, а также – почти любым местам, которые могут хранить значения.

Основы переменных

Как и в других языках, в Common Lisp переменные являются именованными местами, которые могут содержать значения. Однако, в Common Lisp переменные не типизированы таким же образом, как в таких языках, как Java или C++. То есть вам не нужно описывать тип объектов, которые может содержать каждая переменная. Вместо этого, переменная может содержать значения любого типа и сами значения содержат информацию о типе, которая может быть использована для проверки типов во время выполнения. Таким образом, Common Lisp является динамически типизированным: ошибки типов выявляются динамически. Например, если вы передадите не число в функцию +, Common Lisp сообщит вам об ошибке типов. С другой стороны, Common Lisp является строго типизированным языком в том смысле, что все ошибки типов будут обнаружены: нет способа представить объект в качестве экземпляра класса, которым он не является3).

Все значения в Common Lisp, по крайней мере концептуально, являются ссылками на объекты4). Поэтому присваивание переменной нового значения изменяет то, на какой объект ссылается переменная (то есть, куда ссылается переменная), но не оказывает никакого влияния на объект, на который переменная ссылалась ранее. Однако, если переменная содержит ссылку на изменяемый объект, вы можете использовать данную ссылку для изменения этого объекта, и это изменение будет видимо любому коду, который имеет ссылку на этот же объект.

Один из способов введения новой переменной вы уже использовали при определении параметров функции. Как вы видели в предыдущей главе, при определении функции с помощью DEFUN список параметров определяет переменные, которые будут содержать аргументы, переданные функции при вызове. Например, следующая функция определяет три переменные для хранения своих аргументов: x, y и z.

(defun foo (x y z) (+ x y z))

При каждом вызове функции, Lisp создает новые привязки (bindings) для хранения аргументов, переданных при вызове этой функции. Привязка является проявлением переменной во время выполнения. Отдельная переменная — сущность, на которую вы можете сослаться в исходном коде своей программы — может иметь множество различных привязок за время выполнения программы. Отдельная переменная даже может иметь множество привязок в одно и то же время: параметры рекурсивной функции, например, связываются заново (rebound) при каждом вызове этой функции.

Другой формой, позволяющей вводить новые переменные, является специальный оператор LET. Шаблон формы LET имеет следующий вид:

(let (variable*)
body-form*)

где каждая variable является формой инициализации переменной. Каждая форма инициализации является либо списком, содержащим имя переменной и форму начального значения, либо, как сокращение для инициализации переменной в значение NIL, просто именем переменной. Следующая форма LET, например, связывает три переменные x, y и z с начальными значениями 10, 20 и NIL:

(let ((x 10) (y 20) z)
  . ..)

При вычислении формы LET сначала вычисляются все формы начальных значений. Затем, перед выполнением форм тела, создаются и инициализируются в соответствующие начальные значения новые привязки. Внутри тела LET имена переменных ссылаются на только что вновь созданные привязки. После LET имена продолжают ссылаются на то, на что они ссылались перед LET (если они на что-то ссылались).

Значение последнего выражения тела возвращается как значение выражения LET. Как и параметры функций, переменные, вводимые LET, связываются заново (rebound) каждый раз, когда поток управления заходит в LET5).

Область видимости (scope) параметров функций и переменных LET — область программы, где имя переменной может быть использовано для ссылки на привязку переменной — ограничивается формой, которая вводит переменную. Такая форма (определение функции или LET) называется связывающей формой (binding form). Как вы скоро увидите, два типа переменных (лексические и динамические) используют два несколько отличающихся механизма области видимости, но в обоих случаях область видимости ограничена связывающей формой.

Если вы записываете вложенные связывающие формы, которые вводят переменные с одинаковыми именами, то привязки внутренних переменных скрывают внешние привязки. Например, при вызове следующей функции для параметра x создается привязка для хранения аргумента функции. Затем первая LET создает новую привязку с начальным значением 2, а внутренняя LET создает еще одну привязку с начальным значением 3. Комментарии справа указывают область видимости каждой привязки.

(defun foo (x)
  (format t "Параметр: ~a~%" x)              (let ((x 2))                                 (format t "Внешний LET: ~a~%" x)           (let ((x 3))                                 (format t "Внутренний LET: ~a~%" x))    (format t "Внешний LET: ~a~%" x))        (format t "Параметр: ~a~%" x))           ; |

Каждое обращение к x будет ссылаться на привязку с наименьшей окружающей областью видимости. Как только поток управления покидает область видимости какой-то связывающей формы, привязка из непосредственно окружающей области видимости перестает скрываться и x ссылается уже на нее. Таким образом, результатом вызова foo будет следующий вывод:

CL-USER> (foo 1)
Параметр: 1
Внешний LET: 2
Внутренний LET: 3
Внешний LET: 2
Параметр: 1
NIL

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

Например, в главе 7 вы встретите цикл DOTIMES, простой цикл-счетчик. Он вводит переменную, которая содержит значение счетчика, увеличивающегося на каждой итерации цикла. Например, следующий цикл, печатающий числа от 0 до 9, связывает переменную x:

(dotimes (x 10) (format t "~d " x))

Ещё одной связывающей формой является вариант LET: LET*. Различие состоит в том, что в LET имена переменных могут быть использованы только в теле LET (части LET, идущей после списка переменных), а в LET* формы начальных значений для каждой переменной могут ссылаться на переменные, введенные ранее в списке переменных. Таким образом, вы можете записать следующее:

(let* ((x 10)
       (y (+ x 10)))
  (list x y))

но не так:

(let ((x 10)
      (y (+ x 10)))
  (list x y))

Однако, вы можете добиться такого же результата при помощи вложенных LET.

(let ((x 10))
  (let ((y (+ x 10)))
    (list x y)))

Лексические переменные и замыкания

По умолчанию все связывающие формы в Common Lisp вводят переменные лексической области видимости (lexically scoped). На переменные лексической области видимости можно ссылаться только в коде, который текстуально находится внутри связывающей формы. Лексическая область видимости должна быть знакома каждому, кто программировал на Java, C, Perl или Python, так как все они предоставляют «локальные» переменные, имеющие лексическую область видимости. Программисты на Algol также должны чувствовать себя хорошо, так как этот язык первым ввел лексическую область видимости в 1960-х.

Однако, лексические переменные Common Lisp несколько искажают понятие лексической переменной, по крайней мере в сравнении с оригинальной моделью Algol. Это искажение проявляется при комбинировании лексической области видимости со вложенными функциями. По правилам лексической области видимости, только код, текстуально находящийся внутри связывающей формы, может ссылаться на лексическую переменную. Но что произойдет, когда анонимная функция содержит ссылку на лексическую переменную из окружающей области видимости? Например, в следующем выражении:

(let ((count 0)) #'(lambda () (setf count (+ 1 count))))

ссылка на count внутри формы LAMBDA допустима в соответствии с правилами лексической области видимости. Однако, анонимная функция, содержащая ссылку, будет возвращена как значение формы LET, и она может быть вызвана с помощью FUNCALL кодом, который не находится в области видимости LET. Так что же произойдет? Как выясняется, если count является лексической переменной, все работает. Привязка count, созданная когда поток управления зашел в форму LET, остается столько, сколько это необходимо, в данном случае до тех пор, пока что-то сохраняет ссылку на функциональный объект, возвращенный формой LET. Анонимная функция называется замыканием (closure), потому что она «замыкается вокруг» привязки, созданной LET.

Ключевым моментом для понимания замыканий является то, что захватывается не значение переменной, а привязка. Поэтому замыкание может не только иметь доступ к значению переменной, вокруг которой оно «замкнуто», но и присваивать ей новые значения, которые будут сохраняться между вызовами замыкания. Например, вы можете захватить замыкание, созданное предыдущим выражением, в глобальную переменную следующим образом:

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))

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

CL-USER> (funcall *fn*)
1
CL-USER> (funcall *fn*)
2
CL-USER> (funcall *fn*)
3

Отдельное замыкание может «замыкаться вокруг» нескольких привязок переменных просто ссылаясь на них. Также множество замыканий могут захватывать одну и ту же привязку. Например, следующее выражение возвращает список трех замыканий, первое из которых увеличивает значение привязки count, вокруг которой оно «замкнуто», второе – уменьшает его, а третье – возвращает текущее значение:

(let ((count 0))
  (list
   #'(lambda () (incf count))
   #'(lambda () (decf count))
   #'(lambda () count)))

Динамические (специальные) переменные

Привязки с лексической областью видимости помогают поддерживать код понятным, путём ограничения области видимости, в которой, буквально говоря, данное имя имеет смысл. Вот почему большинство современных языков программирования используют лексическую область видимости для локальных переменных. Однако, иногда вам действительно может понадобиться глобальная переменная – переменная, к который вы можете обратиться из любой части своей программы. Хотя неразборчивое использование глобальных переменных может привести к «спагетти-коду» также быстро как и неумеренное использование goto, глобальные переменные имеют разумное использование и существуют в том или ином виде почти в каждом языке программирования6). И, как вы сейчас увидите, глобальные переменные Lisp – динамические переменные – одновременно и более удобны, и более гибки.

Common Lisp предоставляет два способа создания глобальных переменных: DEFVAR и DEFPARAMETER. Обе формы принимают имя переменной, начальное значение и опциональную строку документации. После создания переменной с помощью DEFVAR или DEFPARAMETER имя может быть использовано где угодно для ссылки на текущую привязку этой глобальной переменной. Как вы заметили в предыдущих главах, по соглашению, глобальные переменные именуются именами, начинающимися и заканчивающимися *. Далее в этой главе вы увидите, почему очень важно следовать этому соглашению по именованию. Примеры DEFVAR и DEFPARAMETER выглядят следующим образом:

(defvar *count* 0
  "Число уже созданных виджетов.")

(defparameter *gap-tolerance* 0.001
  "Допустимое отклонение интервала между виджетами.")

Различие между этими двумя формами состоит в том, что DEFPARAMETER всегда присваивает начальное значение названной переменной, а DEFVAR делает это только если переменная не определена. Форма DEFVAR также может использоваться без начального значения для определения глобальной переменной без установки ее значения. Такая переменная называется несвязанной (unbound).

На деле, вам следует использовать DEFVAR для определения переменных, которые будут содержать данные, которые вы хотите сохранять даже при изменениях исходного кода, использующего эту переменную. Например, представьте, что две переменные, определенные ранее, являются частью приложения управления «фабрикой виджетов»7). Правильным будет определить переменную *count* с помощью DEFVAR, так как число уже созданных виджетов не становится недействительным лишь потому, что мы сделали некоторые изменения в коде создания виджетов8).

С другой стороны, переменная *gap-tolerance* вероятно влияет некоторым образом на поведение самого кода создания виджетов. Если вы решите, что вам нужно меньшее или большее допустимое отклонение и, следовательно, измените значение в форме DEFPARAMETER, вы захотите, чтобы изменение вступило в силу при перекомпиляции и перезагрузке файла.

После определения переменной с помощью DEFVAR или DEFPARAMETER вы можете ссылаться на нее откуда угодно. Например, вы можете определить следующую функцию для увеличения числа созданных виджетов:

(defun increment-widget-count () (incf *count*))

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

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

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

Это работает замечательно, пока вы не забудете восстановить исходное значение *standard-output* после завершения действий. Если вы забудете восстановить *standard-output*, весь остальной код программы, использующий *standard-output*, также будет слать свой вывод в файл9).

Но похоже, что то, что вам действительно нужно, это способ обернуть часть кода во что-то говорящее: «Весь нижележащий код (все функции, которые он вызывает, все функции, которые вызывают эти функции, и так далее до функций самого низкого уровня) должны использовать это значение для глобальной переменной *standard-output*». А затем, по завершении работы функции верхнего уровня, старое значение *standard-output* должно быть автоматически восстановлено.

Оказывается, что это именно те возможности, что предоставляет вам другой вид переменных Common Lisp: динамические переменные. Когда вы связываете динамическую переменную, например с LET-переменной или с параметром функции, привязка, создаваемая во время входа в связывающую форму, заменяет глобальную привязку на все время выполнения связывающей формы. В отличие от лексических привязок, к которым можно обращаться только из кода, находящегося в лексической области видимости связывающей формы, к динамическим привязкам можно обращаться из любого кода, вызываемого во время выполнения связывающей формы10). И оказывается, что все глобальные переменные на самом деле являются динамическими.

Таким образом, если вы хотите временно переопределить *standard-output*, это можно сделать просто пересвязав ее, например, с помощью LET.

(let ((*standard-output* *some-other-stream*))
  (stuff))

В любом коде, который выполняется в результате вызова stuff, ссылки на *standard-output* будут использовать привязку, установленную с помощью LET. А после того как stuff завершится и поток управления покинет LET, новая привязка *standard-output* исчезнет и последующие обращения к *standard-output* будут видеть привязку, бывшую до LET. В любой момент времени самая последняя установленная привязка скрывает все остальные. Можно представить, что каждая новая привязка данной динамической переменной помещается в стек привязок этой переменной и ссылки на эту переменную всегда используют последнюю установленную привязку. После выхода из связывающей формы созданные в ней привязки убираются из стека, делая видимыми предыдующие привязки11).

Простой пример показывает как это работает:

(defvar *x* 10)
(defun foo () (format t "X: ~d~%" *x*))

DEFVAR создает глобальную привязку переменной *x* со значением 10. Обращение к *x* в foo будет искать текущую привязку динамически. Если вы вызовете foo на верхнем уровне (from the top level), глобальная привязка, созданная DEFVAR, будет единственной доступной привязкой, поэтому будет напечатано 10.

CL-USER> (foo)
X: 10
NIL

Но вы можете использовать LET для создания новой привязки, которая временно скроет глобальную привязку, и foo напечатает другое значение.

CL-USER> (let ((*x* 20)) (foo))
X: 20
NIL

Теперь снова вызовем foo без LET, она опять будет видеть глобальную привязку.

CL-USER> (foo)
X: 10
NIL

Теперь определим новую функцию.

(defun bar ()
  (foo)
  (let ((*x* 20)) (foo))
  (foo))

Обратите внимание, что средний вызов foo находится внутри LET, которая связывает *x* с новым значением 20. При вызове bar вы получите следующий результат:

CL-USER> (bar)
X: 10
X: 20
X: 10
NIL

Как вы можете заметить, первый вызов foo видит глобальную привязку со значением 10. Средний вызов видит новую привязку со значением 20. А после LET, foo снова видит глобальную привязку.

Как и с лексической привязкой, присваивание нового значения влияет только на текущую привязку. Чтобы увидеть это, вы можете переопределить foo, добавив присваивание значения переменной *x*.

(defun foo ()
  (format t "Перед присваиванием~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "После присваивания~18tX: ~d~%" *x*))

Теперь foo печатает значение *x*, увеличивает его на единицу, а затем печатает его снова. Если вы просто запустите foo, вы увидите следующее:

CL-USER> (foo)
Перед присваиванием X: 10
После присваивания X: 11
NIL

Ничего удивительного. Теперь запустим bar.

CL-USER> (bar)
Перед присваиванием X: 11
После присваивания X: 12
Перед присваиванием X: 20
После присваивания X: 21
Перед присваиванием X: 12
После присваивания X: 13
NIL

Обратите внимание, начальное значение *x* равно 11: предыдущий вызов foo действительно изменил глобальное значение. Первый вызов foo из bar увеличивает глобальную привязку до 12. Средний вызов не видит глобальную привязку из-за LET. А затем последний вызов снова может видеть глобальную привязку и увеличивает ее с 12 до 13.

Так как это работает? Как LET знает, когда связывает *x*, что подразумевается создание динамической привязки вместо обычной лексической? Она знает, так как имя было объявлено специальным12). Имя каждой переменной, определенной с помощью DEFVAR и DEFPARAMETER автоматически глобально объявляется специальным. Это означает, что когда бы вы не использовали это имя в связывающей форме (в форме LET, или как параметр функции, или в любой другой конструкции, которая создает новую привязку переменной, вновь создаваемая привязка будет динамической. Вот почему *соглашение* *по* *именованию* так важно: будет не очень хорошо, если вы используете имя, о котором вы думаете как о лексической переменной, а эта переменная окажется глобальной специальной. С одной стороны, код, который вы вызываете, сможет изменить значение этой связи; с другой, вы сами можете скрыть связь, установленную кодом, находящимся выше по стеку. Если вы всегда будете именовать глобальные переменные, используя соглашение по именованию *, вы никогда случайно не воспользуетесь динамической связью, желая создать лексическую.

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

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

Константы

Еще одним видом переменных, вообще не упомянутых ранее, являются оксюморонические «константные переменные». Все константы являются глобальными и определяются с помощью DEFCONSTANT. Базовая форма DEFCONSTANT подобна DEFPARAMETER.

(defconstant name initial-value-form [ documentation-string ])

Как и в случае с DEFPARAMETER, DEFCONSTANT оказывает глобальный эффект на используемое имя: после этого, имя может быть использовано только для обращения к константе; оно не может быть использовано как параметр функции или быть пересвязано с помощью любой другой связывающей формы. Поэтому многие программисты на Lisp следуют соглашению по именованию и используют для констант имена начинающиеся и заканчивающиеся знаком +. Этому соглашению следуют немного в меньшей степени, чем соглашению для глобальных динамических имен, но оно является хорошей идеей по сходным причинам14).

Ещё один важный момент: несмотря на то что язык позволяет вам переопределять константы путем перевычисления DEFCONSTANT с другой формой начального значения, не определено то, что именно произойдет после такого переопределения. На практике, большинство реализаций требуют, чтобы вы перевычислили любой код, ссылающийся на константу, чтобы изменение вступило в силу, так как старое значение могло быть встроено (inlined). Следовательно, правильным будет использовать DEFCONSTANT для определения только тех вещей, которые действительно являются константами, такие как значение NIL. Для вещей, которые вам может когда-нибудь понадобиться изменить, следует использовать DEFPARAMETER.

Присваивание

После создания привязки вы можете совершать с ней два действия: получить текущее значение и установить ей новое значение. Как вы видели в главе 4, символ вычисляется в значение переменной, которую он именует, поэтому вы можете получить текущее значение просто обратившись к переменной. Для присваивания нового значения привязке используйте макрос SETF, являющийся в Common Lisp оператором присваивания общего назначения. Базовая форма SETF следующая:

(setf place value)

Так как SETF является макросом, он может оценить форму «места», которому он осуществляет присваивание и расшириться (expand) в соответствующие низкоуровневые операции, осуществляющие необходимые действия. Когда «место» является переменной, этот макрос расширяется в вызов специального оператора SETQ, который, как специальный оператор, имеет доступ и к лексическим, и к динамическим привязкам15). Например, для присваивания значения 10 переменной x вы можете написать это:

(setf x 10)

Как я рассказал ранее, присваивание нового значения привязке не оказывает никакого влияния на остальные привязки этой переменной. И оно не оказывает никакого влияния на значение, которое хранилось в привязке до присваивания. Таким образом, SETF в следующей функции:

(defun foo (x) (setf x 10))

не окажет никакого влияния на любое значение вне foo. Привязка, которая создается при вызове foo, устанавливается в 10, незамедлительно заменяя то значение, что было передано в качестве аргумента. В частности, следующая форма:

(let ((y 20))
  (foo y)
  (print y))

напечатает 20, а не 10, так как именно оно является значением y, которое передается foo, где уже является значением переменной x перед тем, как SETF дает x новое значение.

SETF также может осуществить последовательное присваивание множеству «мест». Например, вместо следующего:

(setf x 1)
(setf y 2)

вы можете записать следующее:

(setf x 1 y 2)

SETF возвращает присвоенное значение, поэтому вы можете вкладывать вызовы SETF как в следующем примере, который присваивает и x, и y одинаковое случайное значение:

(setf x (setf y (random 10)))

Обобщенное присваивание

Привязки переменных, конечно, не являются единственными «местами», которые могут содержать значения. Common Lisp поддерживает составные структуры данных, такие как массивы, хэш-таблицы, списки, а также определенные пользователем структуры данных — такие структуры состоят из множества «мест», способных содержать значения.

Я опишу эти структуры данных в последующих главах, но так как мы рассматриваем присваивание, вы должны знать, что SETF может присвоить значение любому «месту». Когда я буду описывать различные составные структуры данных, я буду указывать, какие функции могут использоваться как «места, обрабатываемые SETF» («SETFable places»). Кратко же можно сказать, что если вам нужно присвоить значение «месту», почти наверняка следует использовать SETF. Возможно даже расширить SETF для того, чтобы он мог осуществлять присваивание определенным пользователем «местам», хотя я не описываю такие возможности16).

В этом отношении SETF не отличается от оператора присваивания = языков, произошедших от C. В этих языках оператор = присваивает новые значения переменным, элементам массивов, полям классов. В языках, таких как Perl и Python, которые поддерживают хэш-таблицы как встроенные типы данных, = может также устанавливать значения элементов хэш-таблицы. Таблица 6-1 резюмирует различные способы, которыми используется = в этих языках.

Таблица 6-1. Присваивание с помощью = в других языках программирования

Присваивание … Java, C, C++ Perl Python
… переменной x = 10; $x = 10; x = 10
… элементу массива a[0] = 10; $a[0] = 10; a[0] = 10
… элементу хэш-таблицы $hash{‘key’} = 10; hash[‘key’] = 10
… полю объекта o.field = 10; $o->{‘field’} = 10; o.field = 10

SETF работает сходным образом: первый «аргумент» SETF является «местом» для хранения значения, а второй предоставляет само значения. Как и с оператором = в этих языках, вы используете одинаковую форму и для выражения «места», и для получения значения17). Таким образом, эквиваленты вышеприведенных в таблице 6-1 присваиваний для Lisp следующие (AREF — функция доступа к массиву, GETHASH осуществляет операцию поиска в хэш-таблице, а field может быть функцией, которая обращается к слоту под именем field определенного пользователем объекта):

Простая переменная:    (setf x 10) 
Массив: (setf (aref a 0) 10)
Хэш-таблица: (setf (gethash 'key hash) 10)
Слот с именем 'field': (setf (field o) 10)

Обратите внимание, что присваиваение с помощью SETF «месту», которое является частью большего объекта, имеет ту же семантику, что и присваивание переменной: «место» модифицируется без оказания какого-либо влияния на объект, который хранился там до этого. И вновь, это подобно тому, как ведет себя = в Java, Perl и Python18).

Другие способы изменения «мест»

В то время как все присваивания можно выразить с помощью SETF, некоторые образцы, включающие присваивания нового значения, основанного на текущем значении, являются достаточно общими для того, чтобы получить свои собственные операторы. Например, вы можете увеличить число с помощью SETF следующим образом:

(setf x (+ x 1))

или уменьшить его так:

(setf x (- x 1))

Но это слегка утомительно по сравнению с стилем C: ++x и

x. Вместо этого вы можете использовать макросы INCF и DECF, которые увеличивают и уменьшают «место» на определенную величину, по умолчанию 1.

(incf x)    === (setf x (+ x 1))
(decf x)    === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))

INCF и DECF являются примерами определенного вида макросов, называемых модифицирующими макросами (modify macros). Модифицирующие макросы являются макросами, построенными поверх SETF, которые модифицируют «места» путем присваивания нового значения, основанного на их текущем значении. Главным преимуществом таких макросов является то, что они более краткие, чем аналогичные операции, записанные с помощью SETF. Вдобавок, модифицирующие макросы определены таким образом, чтобы быть безопасными при использовании с «местами», когда выражение «места» должно быть вычислено лишь единожды. Несколько надуманным примером является следующее выражение, которое увеличивает значение произвольного элемента массива:

(incf (aref *array* (random (length *array*))))

Наивный перевод этого примера в выражение, использующее SETF, может выглядить следующим образом:

(setf (aref *array* (random (length *array*)))
      (1+ (aref *array* (random (length *array*)))))

Однако, это не работает, так как два последовательных вызова RANDOM не обязательно вернут одинаковое значение: это выражение вероятно получит значение одного элемента массива, увеличит его, а затем сохранит его как новое значение другого элемента массива. Однако, выражение INCF сделает все правильно, так как знает, как правильно разобрать это выражение:

(aref *array* (random (length *array*)))

чтобы извлечь те части, которые возможно могут иметь побочные эффекты, и гарантировать, что они будут вычисляться лишь один раз. В этом случае, выражение INCF вероятно расширится в нечто более или менее подобное этому:

(let ((tmp (random (length *array*))))
  (setf (aref *array* tmp) (1+ (aref *array* tmp))))

Вообще, модифицирующие макросы гарантируют однократное вычисление слева направо своих аргументов, а также подформ формы места (place form).

Макрос PUSH, который вы использовали в примере с базой данных для добавления элементов в переменную *db*, является еще одним модифицирующим макросом. Более подробно о его работе и работе POP и PUSHNEW будет сказано в главе 12, где я буду говорить о том, как представляются (represented) списки в Lisp.

И наконец, два слегка эзотерических, но полезных модифицирующих макроса – ROTATEF и SHIFTF. ROTATEF циклически сдвигает значение между «местами». Например, если вы имеете две переменные, a и b, этот вызов:

(rotatef a b)

обменяет значения двух переменных и вернет NIL. Так как a и b являются переменными и вам не нужно беспокоиться о побочных эффектах, предыдущее выражение ROTATEF эквивалентно следующему:

(let ((tmp a)) (setf a b b tmp) nil)

С другими видами «мест» эквивалентное выражение с использованием SETF может быть более сложным.

SHIFTF подобен ROTATEF за исключением того, что вместо циклического сдвига значений, он просто сдвигает их влево: последний аргумент предоставляет значение, которое перемещается в предпоследний аргумент и так далее. Исходное значение первого аргумента просто возвращается. Таким образом, следующее:

(shiftf a b 10)

эквивалентно (и снова, так как вам не нужно беспокоиться о побочных эффектах) следующему:

(let ((tmp a)) (setf a b b 10) tmp)

И ROTATEF, и SHIFTF могут использоваться с любым числом аргументов, и, как все модифицирующие макросы, гарантируют однократное их вычисление слева направо.

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

Глобальные переменные терминала — Справочник MQL5

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

Не следует путать глобальные переменные клиентского терминала с переменными, объявленными на глобальном уровне mql5-программы.

Глобальные переменные существуют в клиентском терминале 4 недели с момента последнего обращения, после этого автоматически уничтожаются. Обращением к глобальной переменной считается не только установка нового значения, но и чтение значения глобальной переменной.

Глобальные переменные клиентского терминала доступны одновременно из всех mql5-программ, запущенных на клиентском терминале.

Функция

Действие

GlobalVariableCheck

Проверяет существование глобальной переменной с указанным именем

GlobalVariableTime

Возвращает время последнего доступа к глобальной переменной

GlobalVariableDel

Удаляет глобальную переменную

GlobalVariableGet

Запрашивает значение глобальной переменной

GlobalVariableName

Возвращает имя глобальной переменной по порядковому номеру в списке глобальных переменных

GlobalVariableSet

Устанавливает новое значение глобальной переменной

GlobalVariablesFlush

Принудительно записывает содержимое всех глобальных переменных на диск

GlobalVariableTemp

Устанавливает новое значение глобальной переменной, которая существует только на время текущего сеанса работы терминала

GlobalVariableSetOnCondition

Устанавливает новое значение существующей глобальной переменной по условию

GlobalVariablesDeleteAll

Удаляет глобальные переменные с указанным префиксом в имени

GlobalVariablesTotal

Возвращает общее количество глобальных переменных

глобальных переменных, extern, static, const

глобальных переменных, extern, static, const

Локальные переменные

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

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

Ключевое слово auto может использоваться для явного создания этих переменных, но не требуется, так как по умолчанию используется auto.

Глобальные переменные и внешние

Глобальная переменная — это переменная, которая определяется вне всех функций и доступен для всех функций.

Эти переменные не зависят от области видимости и всегда доступны, что означает что глобальная переменная существует до завершения программы.

Можно создать глобальную переменную в одном файле и обращаться к ней из другой файл.Для этого переменная должна быть объявлена ​​в обоих файлах, но ключевое слово extern должно предшествовать «второму» объявлению.

Статические переменные

Статическая переменная может быть глобальной или локальной переменной. Оба создается путем предшествования объявлению переменной ключевого слова static .

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

Когда создается локальная статическая переменная, ей должно быть присвоено начальное значение. В противном случае значение по умолчанию будет 0.

Глобальная статическая переменная — это переменная, к которой можно получить доступ только в файле где он создан. Считается, что эта переменная имеет область видимости файла .

Постоянные переменные

В C директива препроцессора #define использовалась для создания переменной с постоянное значение. Это все еще работает в C ++, но могут возникнуть проблемы.

Когда используется #define, препроцессор выполнит код и заменит каждый экземпляр переменной #defined с соответствующим значением. Ну так как переменная #defined существует только в том файле, где она создана, это возможно иметь такое же определение в другом файле с совершенно другим значением. Этот может привести к плачевным последствиям.

Чтобы преодолеть эту проблему, концепция именованной константы, которая похожа на переменная была введена в C ++.

Чтобы создать постоянную переменную в C ++, перед объявлением переменной укажите ключевое слово const . Это сообщает компилятору, что «переменная была создано значение, которое не может быть изменено «

При создании постоянной переменной ей ДОЛЖНО быть присвоено значение.

Разница между локальной и глобальной переменной

Подробности

Что такое переменная?

Переменная — это имя, присвоенное области памяти, которой программа может управлять.Тип переменной определяет размер и структуру памяти переменной.

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

В этом руководстве вы узнаете

Область действия переменных

Область действия переменной — это просто время жизни переменной. Это блок кода, в котором переменная применима или активна. Например:

 function foo () {
var x;
}
 

Вы объявляете переменную «x» внутри функции «foo.»Область этой переменной остается внутри этой функции, ее нельзя использовать вне этой функции.

Есть три места, где переменные можно объявлять на языке программирования переменных:

  • Внутри функции или блока: Локальные переменные
  • Вне всех функций: Глобальные переменные
  • В определении параметров функции: Формальные параметры

КЛЮЧЕВАЯ РАЗНИЦА

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

Локальная переменная

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

Пример локальной переменной

 public int add () {
int a = 4;
int b = 5;
вернуть a + b;
}
 

Здесь «a» и «b» — это локальные переменные

Глобальная переменная

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

Пример:

 int a = 4;
int b = 5;
public int add () {
вернуть a + b;
}
 

Здесь «a» и «b» — глобальные переменные.

Локальная переменная Vs. Глобальные переменные

Вот некоторые фундаментальные различия между локальными и глобальными переменными.

Параметр Локальный Глобальный
Область действия Он объявлен внутри функции. Объявлен вне функции.
Значение Если оно не инициализировано, значение мусора сохраняется. Если оно не инициализировано, по умолчанию сохраняется ноль.
Время жизни Создается, когда функция начинает выполнение, и теряется при завершении функции. Он создается до начала глобального выполнения программы и теряется при завершении программы.
Совместное использование данных Совместное использование данных невозможно, поскольку к данным локальной переменной можно получить доступ только одной функцией. Совместное использование данных возможно, поскольку несколько функций могут обращаться к одной и той же глобальной переменной.
Параметры Передача параметров требуется для локальных переменных для доступа к значению в другой функции Передача параметров не требуется для глобальной переменной, поскольку она видна во всей программе
Изменение значения переменной Когда значение локальной переменной изменяется в одной функции, изменения не видны в другой функции. Когда значение глобальной переменной изменяется в одной функции, изменения видны в остальной части программы.
Доступ к Доступ к локальным переменным можно получить с помощью операторов внутри функции, в которой они объявлены. Вы можете получить доступ к глобальным переменным с помощью любого оператора в программе.
Память Хранится в стеке, если не указано иное. Он хранится в фиксированном месте, определенном компилятором.

Преимущества использования глобальных переменных

  • Вы можете получить доступ к глобальной переменной из всех функций или модулей в программе
  • Вам нужно только объявить глобальную переменную однократно вне модулей.
  • Он идеально подходит для хранения «констант», так как помогает сохранить согласованность.
  • Глобальная переменная полезна, когда несколько функций обращаются к одним и тем же данным.

Преимущества использования локальных переменных

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

Недостатки использования глобальных переменных

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

Недостатки использования локальных переменных

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

Что еще полезнее?

Локальная и глобальная переменные одинаково важны при написании программы на любом языке. Однако большое количество глобальных переменных может занимать огромную память. Нежелательное изменение глобальных переменных становится трудно идентифицировать. Поэтому рекомендуется избегать объявления нежелательных глобальных переменных.

(PDF) Рефакторинг исходного кода и исключение глобальных переменных в программах на C

Рефакторинг исходного кода и устранение глобальных переменных в программах на языке C

272

Рисунок 10. Соотношение динамического числа команд и запрограммируйте среду выполнения

до и после преобразования локализации.

небольшое количество перемещаемых глобальных переменных обычно не претерпевает существенных изменений

. Однако динамическое количество команд

действительно демонстрирует значительное ухудшение в случаях

, когда преобразование локализует большое количество глобальных переменных

и / или значительно увеличивает количество аргументов функции

(как показано на рисунке 5).В эту категорию попадают несколько программ тестирования производительности

, включая dijkstra, ispell, string-

search и 458.sjeng. Интересно, что

мы наблюдаем, что в большинстве случаев увеличение числа динамических

инструкций не приводит к соответствующему увеличению на

фактического времени выполнения теста. Наиболее заметным исключением из этого наблюдения является tiff2rgba, который после преобразования локализации существенно снизился на

по сравнению с исходным временем выполнения программы

.Помните, что

tiff2rgba — единственная программа, не скомпилированная с оптимизацией

-O2 GCC. Таким образом, кажется, что оптимизация для

, сформированная GCC и микроархитектурой x86, хорошо справляется с задачей уменьшения накладных расходов, вызванных нашим формированием trans-

для локализации и устранения глобальных переменных.

6. Дальнейшая работа

Существует ряд возможных улучшений. Во-первых, мы планируем улучшить анализ указателя и анализ псевдонимов в компиляторе

Clang, чтобы соответствующим образом разрешить косвенные вызовы функций

и построить точные графы вызовов.Во-вторых, мы разрабатываем

интерактивную среду на основе Eclipse, чтобы

пользователь мог выборочно локализовать только важные глобальные переменные

. Мы также планируем разработать алгоритмы машинного обучения

, чтобы автоматически находить наиболее многообещающие глобалы

для локализации. В-третьих, мы продолжим исследование

причин снижения производительности и разработаем оптимизацию

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

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

7. Заключение

В этой статье мы представляем основанный на компиляторе инструмент преобразования

исходного кода и рефакторинга для автоматического преобразования глобальных переменных в локальные переменные. Наш алгоритм преобразования

автоматически обнаруживает каждую глобальную переменную

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

, которые устанавливают / используйте его, а затем измените все операторы программы

, которые использовали исходную глобальную переменную, чтобы теперь

вместо этого использовали соответствующий локальный аргумент или.Мы также анализируем статические эффекты и эффекты времени выполнения нашего локализованного преобразования

. Мы обнаружили, что во многих тестах

широко используются глобальные переменные. Однако большинство глобальных переменных

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

, которые расположены близко друг к другу в графике вызовов функции

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

, что может помочь усилиям по проверке кода.В то время как калибровочное преобразование lo-

может повлиять на количество и распределение пространства памяти, используемого данными и областями стека

адресного пространства процесса, локализация большинства глобальных переменных

оказывает лишь незначительное ухудшающее воздействие на

.

производительность во время выполнения.

СПИСОК ЛИТЕРАТУРЫ

[1] Б. В. Керниган и Д. М. Ричи, «Программа C —

ming Language», Prentice-Hall, Inc., Upper Saddle River,

1978.

[2] В. Вульф и М. Шоу, «Глобальная переменная

считается вредной», Уведомления ACM SIGPLAN, Vol. 8, No. 2, 1973,

pp. 28-34.

[3] Г. Кляйн, К. Эльфинстон, Г. Хейзер, Дж. Андроник, Д.

Кок, П. Деррин, Д. Элькадуве, К. Энгельхардт, Р. Колан —

лыжи, М. Норриш , Т. Сьюэлл, Х. Тач и С. Винвуд,

«SEL4: формальная проверка ядра ОС», Материалы 22-го симпозиума ACM SIGOPS по системным принципам работы

, SOSP’09, Нью-Йорк,

. , 11-14 октября

2009, стр.207-220,

[4] Дж. Барнс, «Программное обеспечение высокой целостности: SPARK Ap-

обеспечивает безопасность и защиту», Addison-Wesley Long-

man Publishing Co., Inc., Бостон, 2003 г.

[5] Д. Бинкли, М. Харман, Ю. Хассун, С. Ислам и З. Ли,

«Оценка воздействия глобальных переменных на программу

Зависимость и кластеры зависимости», журнал Sys-

темы и программное обеспечение, Vol. 83, No. 1, 2010, pp. 96-107.

DOI: 10.1016 / j.jss.2009.03.038

[6] Ф. Балмас, «Использование графов зависимостей в качестве поддержки для программ документов

», Труды второго международного семинара IEEE

по анализу исходного кода и манипуляциям

, SCAM’02, Монреаль, 1 октября 2002 г., стр.

45-154

[7] Й. Денг, С. Котари и Ю. Намара, «Program Slice

Browser», Труды 9-го международного семинара

по пониманию программ, Торонто, 12-13 мая 2001 г.,

стр.50-59.

[8] С. Блэк, «Эффект пульсации вычислений для основного программного обеспечения —

», Журнал сопровождения программного обеспечения, Vol. 13, No.

4, 2001, pp. 263-279. doi: 10.1002 / smr.233

Авторские права © 2013 SciRes. JSEA

Глобальные переменные допустимы и фактически необходимы

Без названия

Проф. Норм Матлофф
Калифорнийский университет, Дэвис
Дэвис, Калифорния 95616
matloff @ cs.ucdavis.edu

Постановка проблемы:

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

Лицемерие всего:

Антиглобалисты скажут, например: «Если вы используете глобальный переменных, трудно сказать, в каких точках вашего кода переменная значение может быть изменено. Гораздо понятнее объявить переменную как local, скажем, в main (), а затем использовать его как параметр в вызовах функций «. Поначалу это может звучать хорошо, но давайте рассмотрим подробнее.

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

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

Рассмотрим, например, учебник «Разработка программ на C ++» Кохуна и Дэвидсон. На стр.255 они строго советуют читателю избегать глобалов, цитируя аргумент «глобальных побочных эффектов», описанный выше. Еще на pp.686ff, главный проект авторов книги, делает именно то, что авторы считают такой плохой стиль. Класс Bug включает переменная-член HitsTaken, которая изменяется внутри члена функция IsHit (), не являясь параметром этой функции.

Точно так же антиглобалисты говорят: «Глобальные переменные — это плохо, потому что потенциальной путаницы, которая может возникнуть, если (случайно или намеренно) имеет локальную переменную с таким же именем.»Верно, но опять же это в равной степени применимо к переменным-членам в контексте ООП, и все же У антиглобалистов нет проблем с использованием ООП.

Конечно, есть разница в степени. Масштаб глобального переменная шире, чем у переменной-члена. Тем не менее проблемы идентичны, и в большом сложном классе степень «неприятности» были бы столь же серьезными.

Глобальные объекты необходимы во многих современных приложениях:

Примерно с 1995 года программирование с потоком стало очень популярный, e.грамм. для веб-приложений и приложений с графическим интерфейсом. Нити похожи на урезанные ОС, но с одним важным отличием в том, что связь между потоки обычно обрабатываются через общие глобальные переменные. Другими словами, Глобальные объекты необходимы в многопоточном программировании .

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

Отладка:

С точки зрения отладки есть как минимум два преимущества реализация переменной как глобальной:

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

4.2 Видимость и время жизни (глобальные переменные и т. Д.)

4.2 Видимость и время жизни (глобальные переменные и т. Д.)

4.2 Видимость и время жизни (глобальные переменные и т. Д.)

Мы не сказали об этом прямо, но переменные — это каналы связи внутри программы. Вы устанавливаете значение переменной в одной точке программы, и в другом месте (или точках) вы снова читаете значение.Эти два пункта могут быть в смежных утверждениях, или они могут быть в сильно разнесенных частях программы.

Как долго длится переменная? Насколько далеко могут быть разделены части установки и загрузки программы, и как долго после установки переменной она сохраняется? В зависимости от переменной и как вы это используете, вы можете захотеть разные ответы на эти вопросы.

Видимость переменной определяет, сколько остальная часть программы может получить доступ к этой переменной.Вы можете сделать так, чтобы переменная была видна только в одной части одной функции, или в одной функции, или в одном исходном файле, или же в любом месте программы. (Мы еще не говорили об исходных файлах; мы будем скоро их исследую.)

Почему вы хотите ограничить видимость переменной? Для максимальной гибкости было бы удобно, если бы все переменные потенциально были видны везде? Так получилось, что будет слишком гибким : везде в программе вам придется отслеживать имена всех переменных, объявленных где-либо еще в программе, чтобы вы случайно не использовали его повторно.Всякий раз, когда переменная по ошибке имела неправильное значение, вы бы искать ошибку во всей программе, потому что любой оператор во всей программе потенциально могла изменить эту переменную. Вы бы постоянно переступали через себя, используя общее имя переменной, например i, в двух частях вашей программы, и если один фрагмент кода случайно перезаписал значения, используемые другой частью кода. Общение было бы похоже на старую вечеринку линия — вы всегда будете случайно перебивать других разговоры или прерывание вашего разговора.

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

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

Другое слово для обозначения переменных — , область действия .

Как долго сохраняются переменные? По умолчанию локальные переменные (объявленные в функции) иметь автомат длительность : они возникают при вызове функции, и они (и их значения) исчезают, когда функция возвращается.С другой стороны, глобальные переменные имеют статическую длительность : они продолжаются, и сохраненные в них значения сохраняются, до тех пор, пока это делает программа. (Конечно, значения могут быть перезаписаны, поэтому они не обязательно сохраняются вечно.)

Наконец, можно разделить функцию на несколько исходные файлы для упрощения обслуживания. Когда несколько исходных файлов объединены в одну программу (мы увидим, как это сделать в следующей главе) компилятор должен иметь возможность соотносить глобальные переменные, которые могут использоваться для связи между несколько исходных файлов.Кроме того, если глобальная переменная будет полезна для связи, должен быть ровно один из них: вам не нужна одна функция в одном исходном файле для хранения значения в одной глобальной переменной с именем globalvar, а затем иметь другую функцию в другом исходном файле читать из другой глобальной переменной с именем globalvar. Следовательно, глобальная переменная должна иметь ровно один определяющий экземпляр , в одном месте в одном исходном файле. Если та же переменная должна использоваться где-либо еще (я.е. в другом исходном файле или файлах), переменная объявлена в этих других файлах с внешней декларацией , что не является определяющим примером. Во внешнем объявлении говорится: « Привет, компилятор, вот имя и тип глобальной переменной, которую я собираюсь использовать, но не определяй это здесь, не выделяйте для этого места; это определено где-то еще, и я просто имею в виду это здесь ». Если у вас случайно есть два разных определяющих экземпляра для одноименная переменная, компилятор (или компоновщик) пожалуется, что это « многократно определяемый.»

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

Вы получаете любой дополнительный контроль над видимостью и время жизни, и вы различаете определяющие экземпляры и внешние объявления, с помощью классы хранения .Класс хранения — дополнительное ключевое слово в начале декларация который каким-то образом изменяет объявление. Как правило, класс хранения (если есть) это первое слово в объявлении, перед именем типа. (Строго говоря, такой заказ традиционно не был необходим, и вы можете увидеть код с классом хранения, именем типа, и другие части декларации в необычном порядке.)

Мы сказали, что по умолчанию локальные переменные имеют автоматическую продолжительность. Чтобы дать им статическую продолжительность (так что вместо того, чтобы приходить и уходить, поскольку функция вызываемые, они сохраняются до тех пор, пока работает функция), вы предшествуете их объявлению ключевым словом static:

статический int i;
 

По умолчанию объявление глобальной переменной (особенно если в нем указано начальное значение) является определяющим экземпляром.Чтобы сделать это внешнее объявление, переменной, которая определена где-то еще, ты поставьте перед ним ключевое слово extern:

extern int j;
 

Наконец, чтобы глобальная переменная была видна только в содержащем его исходном файле, ты поставьте перед ним ключевое слово static:

статический int k;
 

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

Подводя итог, мы обсудили два разных атрибута переменная: видимость и продолжительность. Они ортогональны, как показано в этой таблице:

глобальные переменные
продолжительность:
видимость: автоматический статический
локальные нормальные локальные переменные статические локальные переменные
глобальные

Мы также можем различать глобальную область видимости файла переменные и действительно глобальные переменные, основанные на наличии или отсутствие ключевого слова static.

Мы также можем различать внешние объявления и определение примеров глобальные переменные, основанные на наличии или отсутствии ключевое слово extern.


Прочтите последовательно: предыдущий следующий вверх верх

Эта страница Стива Саммита // Авторское право 1995-1997 гг. // отправить отзыв по электронной почте

глобальных переменных — зло

глобальные переменные — зло

Глобальные переменные

Глобальная переменная — это переменная, определенная в «основной» программе. Такие переменные имеют «глобальную» область видимости.

Локальная переменная — это переменная, определенная в функции. Говорят, что такие переменные имеют локальную «область видимости».

Функции могут обращаться к глобальным переменным и изменять их.

Изменение глобальных переменных в функции считается плохой практикой программирования. Лучше отправить переменную в качестве параметра (или вернуть ее в операторе return).

Модульное программирование

Думайте о функции как о черном ящике со входами и выходами.

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

подпись функции — имя функции и список параметров (и вернуть спецификацию на таких языках, как java), например,

def pow (x, n) : # эта строка сигнатура функции
total = 1
count = 0
while count total = total * x
count = count + 1
return total # в Java возврат значение также должно быть указано в сигнатуре функции

# main
result = pow (4,2) # если модульный, мы можем предположить эффекты pow только результат, без других побочных эффектов
результат печати

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

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

Единственный эффект функции — это возвращаемое значение и модификации список / параметры объекта (скаляры не могут быть изменены).

Правила области действия Python

Внутри определения функции,

если переменная считана , Python проверяет, определена ли она локально.Если нет, Python проверит, есть ли глобальный с это имя.

def func (): # читать пример
print x # python будет использовать глобальный x, если он существует

если переменная записывается, для нее создается локальный слот.

def func (): # напишите пример
x = 7 # не будет использовать global, даже если существует глобальный ‘x’.

если вы хотите писать в глобальную переменную, вы должны использовать «глобальную» утверждение.

def func (): # как писать в глобальный
глобальный x
x = x + 1 # изменяет глобальный x

Рассмотрим следующие фрагменты программы. Какой результат?

по умолчанию f ():

х = 10

x = 5
f ()
печать x

*************

по умолчанию f ():

отпечатка x

x = 5
f ()

********

по умолчанию f ():

глобально x
x = 10

x = 5
f ()
печать x

*************

по умолчанию f ():

х = х + 1

x = 5
f ()
печать x

************

по умолчанию f ():

лист.добавить (5)

список = [1,2,3,4]
f ()
распечатать список

*************

по умолчанию f ():

list = []
list.append (5)

список = [1,2,3,4]
f ()
распечатать список

******************

Рабочий лист для занятий в классе

1. Перепишите pow и main, чтобы сделать их ЗЛОЙ, то есть НЕ МОДУЛЬНОЙ. Просто нарисуй это вне, не программируйте.

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

# 1
def generateSecret ():
secretCode = []
i = 0
while i <4:
color = случайный.рандинт (0,5)
secretCode.append (цвета [цвет])
i = i + 1

# вызов основной функции
colors = [‘r’, ‘b’, ‘g’, ‘p’, ‘y’, ‘b’]
generateSecret ()
print secretCode;

# 2
def generateSecret ():

i = 0
while i <4:
color = random.randint (0,5)
secretCode.append (цвета [цвет])
i = i + 1

# вызов основной функции:

цветов = [‘r’, ‘b’, ‘g’, ‘p’, ‘y’, ‘b’]
secretCode = []
generateSecret ()
print secretCode

# 3
def generateSecret ():
secretCode = []
i = 0
while i <4:
color = случайный.рандинт (0,5)
secretCode.append (colors [color])
i = i + 1
вернуть secretCode

# вызов основной функции:

colors = [‘r’, ‘b’, ‘g’, ‘p’, ‘y’, ‘b’]
secretCode = generateSecret ()
print secretCode

3. Перепишите №3 так, чтобы он был полностью модульным.


.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *