Сумасбродство глобальной переменной Python
У вас есть три файла: main.py, second.py и common.py
common.py
#!/usr/bin/python
GLOBAL_ONE = "Frank"
main.py
#!/usr/bin/python
from common import *
from second import secondTest
if __name__ == "__main__":
global GLOBAL_ONE
print GLOBAL_ONE #Prints "Frank"
GLOBAL_ONE = "Bob"
print GLOBAL_ONE #Prints "Bob"
secondTest()
print GLOBAL_ONE #Prints "Bob"
second.py
#!/usr/bin/python
from common import *
def secondTest():
global GLOBAL_ONE
print GLOBAL_ONE #Prints "Frank"
Почему secondTest не использует глобальные переменные своей вызывающей программы? Какой смысл называть что-то "глобальным", если на самом деле это не так??
Что мне не хватает, чтобы получить secondTest (или любую внешнюю функцию, которую я вызываю из main), чтобы распознавать и использовать правильные переменные?
Ответы
Ответ 1
global
означает глобальный для этого модуля, а не для всей программы. Когда вы делаете
from lala import *
вы добавляете все определения lala
в качестве локальных пользователей в этот модуль.
Итак, в вашем случае вы получаете две копии GLOBAL_ONE
Ответ 2
Первый и очевидный вопрос: почему?
Есть несколько ситуаций, в которых глобальные переменные необходимы/полезны, но их действительно мало.
Ваша проблема связана с пространствами имен. Когда вы импортируете common в second.py, GLOBAL_ONE
поступает из этого пространства имен. Когда вы импортируете secondTest, он по-прежнему ссылается на GLOBAL_ONE
на common.py.
Ваша настоящая проблема, однако, связана с дизайном. Я не могу придумать ни единой логической причины для реализации глобальной переменной таким образом. Глобальные переменные - сложный бизнес на Python, потому что нет такой вещи, как постоянная переменная. Однако соглашением является то, что когда вы хотите сохранить что-то постоянное в Python, вы назовете его WITH_ALL_CAPS
. Ergo:
somevar = MY_GLOBAL_VAR # good!
MY_GLOBAL_VAR = somevar # What? You "can't" assign to a constant! Bad!
Есть много причин, которые делают что-то вроде этого:
earth = 6e24
def badfunction():
global earth
earth += 1e5
print '%.2e' % earth
ужасно.
Конечно, если вы просто делаете это как упражнение для понимания пространств имен и вызова global
, продолжайте.
Если нет, некоторые из причин, по которым глобальные переменные являются A Bad Thing ™:
- Загрязнение пространства имен
- Функциональная интеграция - вы хотите, чтобы ваши функции были разделены на части.
- Функциональные побочные эффекты - что происходит, когда вы пишете функцию, которая изменяет глобальную переменную
balance
, и либо вы, либо кто-то другой повторно используете вашу функцию и не учитываете это? Если вы рассчитываете баланс счета, внезапно у вас слишком много или недостаточно. Такие ошибки трудно найти.
Если у вас есть функция, которая нуждается в значении, вы должны передать это значение в качестве параметра, если у вас нет по-настоящему веской причины. Одна из причин заключалась бы в глобальном PI - в зависимости от ваших потребностей в точности, вы можете захотеть, чтобы она была 3.14
, или вам может понадобиться ее 3.14159265
... но это тот случай, когда глобальность имеет смысл. Вероятно, есть только несколько или два реальных случая, которые могут правильно использовать глобальные переменные. Один из случаев - это константы в программировании игр. Легче импортировать pygame.locals и использовать KP_UP, чем помнить целочисленное значение, отвечающее за это событие. Это исключения из правила.
И (по крайней мере, в pygame) эти константы хранятся в отдельном файле - только для констант. Любой модуль, который нуждается в этих константах, будет импортировать указанные константы.
Когда вы программируете, вы пишете функции, чтобы разбить проблему на управляемые куски. Предпочтительно функция должна делать одно и не иметь побочных эффектов. Это означает, что функция, такая как calculatetime(), должна вычислять время. Вероятно, он не должен читать файл, содержащий время, и запрещать ему делать что-то вроде записи времени где-то. Он может вернуть время и принимать параметры, если он им нужен - оба из них - хорошие, приемлемые вещи для выполнения функций. Функции - это своего рода контракт между вами (программистом функции) и любым (включая вас), который использует эту функцию. Доступ и изменение глобальных переменных являются нарушением этого контракта, поскольку функция может изменять внешние данные способами, которые не определены или не ожидаются. Когда я использую эту функцию calculatetime()
, я ожидаю, что она вычислит время и, возможно, вернет его, а не изменит время глобальной переменной, которое отвечает на время модуля, которое я только что импортировал.
Изменение глобальных переменных нарушает контракт и логическое различие между действиями, которые выполняет ваша программа. Они могут вводить ошибки в вашу программу. Они затрудняют обновление и изменение функций. Когда вы используете глобальные переменные вместо переменных, смерть ждет вас острыми острыми зубами!
Ответ 3
Сравните результаты следующего с вашим. Когда вы используете правильные пространства имен, вы получите ожидаемые результаты.
common.py
#!/usr/bin/python
GLOBAL_ONE = "Frank"
main.py
#!/usr/bin/python
from second import secondTest
import common
if __name__ == "__main__":
print common.GLOBAL_ONE # Prints "Frank"
common.GLOBAL_ONE = "Bob"
print common.GLOBAL_ONE # Prints "Bob"
secondTest()
print common.GLOBAL_ONE # Prints "Bob"
second.py
#!/usr/bin/python
import common
def secondTest():
print common.GLOBAL_ONE # Prints "Bob"
Ответ 4
Позвольте мне сначала сказать, что я согласен со всеми, кто ответил, прежде чем сказать, что это, вероятно, не то, что вы хотите сделать. Но в случае, если вы действительно уверены, что это путь, вы можете сделать следующее. Вместо определения GLOBAL_ONE
как строки в common.py
, определите его как список, то есть GLOBAL_ONE = ["Frank"]
. Затем вы читаете и изменяете GLOBAL_ONE[0]
вместо GLOBAL_ONE
, и все работает так, как вы хотите. Заметьте, что я не думаю, что это хороший стиль, и есть, вероятно, лучшие способы добиться того, чего вы действительно хотите.