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

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

...
__DBNAME__ = None

def initDB(name):
    if not __DBNAME__:
        __DBNAME__ = name
    else:
        raise RuntimeError("Database name has already been set.")
...

И после импорта модуля в другой файл

...
import mymodule
mymodule.initDB('mydb.sqlite')
...

И трассировка была:

... UnboundLocalError: локальная переменная 'DBNAME', на которую имеется ссылка перед назначением ...

Любые идеи? Я пытаюсь настроить синглтон с помощью модуля в соответствии с рекомендация этого человека.


person daveslab    schedule 29.12.2009    source источник


Ответы (5)


Вот что происходит.

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

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

Представьте себе файл с именем foo.py, содержащий эту единственную строку:

X = 1

А теперь представьте, что вы его импортируете.

import foo
print(foo.X)  # prints 1

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

def initDB(name):
    global __DBNAME__  # add this line!
    if __DBNAME__ is None: # see notes below; explicit test for None
        __DBNAME__ = name
    else:
        raise RuntimeError("Database name has already been set.")

Между прочим, для этого примера достаточно простого if not __DBNAME__ теста, потому что любое строковое значение, кроме пустой строки, будет оценивать как истину, поэтому любое фактическое имя базы данных будет оценивать как истину. Но для переменных, которые могут содержать числовое значение, которое может быть 0, вы не можете просто сказать if not variablename; в этом случае вы должны явно проверить None с помощью оператора is. Я изменил пример, добавив явный None тест. Явный тест для None никогда не ошибается, поэтому я по умолчанию использую его.

Наконец, как другие отмечали на этой странице, два ведущих подчеркивания сигнализируют Python о том, что вы хотите, чтобы переменная была «частной» для модуля. Если вы когда-нибудь сделаете import * from mymodule, Python не будет импортировать имена с двумя ведущими символами подчеркивания в ваше пространство имен. Но если вы просто выполните простой import mymodule, а затем произнесете dir(mymodule), вы увидите «частные» переменные в списке, а если вы явно ссылаетесь на mymodule.__DBNAME__ Python, это не заботит, он просто позволит вам ссылаться на них. Двойное подчеркивание в начале является основным ключом к пониманию пользователей вашего модуля, что вы не хотите, чтобы они привязывали это имя к какому-либо собственному значению.

В Python считается лучшей практикой не делать import *, а минимизировать взаимосвязь и максимизировать явность, используя mymodule.something или явно выполняя импорт, например from mymodule import something.

РЕДАКТИРОВАТЬ: Если по какой-то причине вам нужно сделать что-то подобное в очень старой версии Python, в которой нет ключевого слова global, есть простой обходной путь. Вместо того, чтобы напрямую устанавливать глобальную переменную модуля, используйте изменяемый тип на глобальном уровне модуля и сохраните свои значения внутри него.

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

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

__DBNAME__ = [None] # use length-1 list as a mutable

# later, in code:  
if __DBNAME__[0] is None:
    __DBNAME__[0] = name

dict лучше. Но наиболее удобен экземпляр класса, и вы можете просто использовать тривиальный класс:

class Box:
    pass

__m = Box()  # m will contain all module-level values
__m.dbname = None  # database name global in module

# later, in code:
if __m.dbname is None:
    __m.dbname = name

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

Мне нравится синтаксический сахар простого использования __m.dbname, а не __m["DBNAME"]; На мой взгляд, это наиболее удобное решение. Но решение dict тоже работает нормально.

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

person steveha    schedule 30.12.2009
comment
Два ведущих символа подчеркивания приведут к искажению имени. Обычно одного подчеркивания достаточно, чтобы указать, что переменную следует считать частной. stackoverflow.com/questions/6930144/ - person H.Rabiee; 30.07.2017
comment
Что касается класса Box, не лучше ли было бы определить dbname = None в функции init, чем вне this, как в примере? - person SuperGeo; 01.10.2017
comment
Python не волнует, как настраиваются переменные. Существуют рецепты для класса Box или аналогичного, которые определяют функцию __init__(), которая берет все значения из kwargs и устанавливает их в словаре класса. Тогда вы можете просто сделать _m = Box(dbname="whatever"), и это будет аккуратно. Начиная с Python 3.3, теперь есть types.SimpleNameSpace, который является полнофункциональной реализацией класса Box; см. docs.python.org/3/ библиотека / - person steveha; 16.10.2017

Явный доступ к переменным уровня модуля путем явного доступа к ним в модуле


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

Думайте об этом как о self для текущего модуля вместо текущего экземпляра!

# db.py
import sys

# this is a pointer to the module object instance itself.
this = sys.modules[__name__]

# we can explicitly make assignments on it 
this.db_name = None

def initialize_db(name):
    if (this.db_name is None):
        # also in local function scope. no scope specifier like global is needed
        this.db_name = name
        # also the name remains free for local use
        db_name = "Locally scoped db_name variable. Doesn't do anything here."
    else:
        msg = "Database is already initialized to {0}."
        raise RuntimeError(msg.format(this.db_name))

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

# client_a.py
import db

db.initialize_db('mongo')
# client_b.py
import db

if (db.db_name == 'mongo'):
    db.db_name = None  # this is the preferred way of usage, as it updates the value for all clients, because they access the same reference from the same module object
# client_c.py
from db import db_name
# be careful when importing like this, as a new reference "db_name" will
# be created in the module namespace of client_c, which points to the value 
# that "db.db_name" has at import time of "client_c".

if (db_name == 'mongo'):  # checking is fine if "db.db_name" doesn't change
    db_name = None  # be careful, because this only assigns the reference client_c.db_name to a new value, but leaves db.db_name pointing to its current value.

В качестве дополнительного бонуса я считаю его в целом довольно питоническим, поскольку он прекрасно соответствует политике Pythons: Явный лучше, чем неявный.

person timmwagener    schedule 09.03.2016
comment
Мне нравится, что вы можете использовать более точный импорт из db во втором модуле, даже если вам нужно сделать больший импорт db в основном. Это кажется правдой, если вы пропустите магию sys и используете global в initialize_db. Можете ли вы прокомментировать плюсы и минусы глобального и вашего ответа, поскольку они оба работают одинаково? - person Alain Collins; 12.12.2016
comment
Для меня плюс в том, что вам больше не нужно манипулировать областью видимости. Вы явно указываете область действия, обращаясь к переменной db_name из объекта, которым оказывается модуль. Вам не нужно объявлять, где находится объект, с которым вы хотите работать, прежде чем использовать его в любое время. Также вы можете иметь локальные переменные с именем db_name в функциях-обработчиках, а также рядом с this.db_name. - person timmwagener; 12.12.2016
comment
Мне кажется, что это самый чистый способ сделать это, но мои линтеры сопротивляются этому. Я делаю что-то не так или у вас / других тоже есть эта проблема? Большое спасибо, Крис - person ThePosey; 09.09.2017
comment
@beeb В моем примере для client_b.py есть небольшая загвоздка. Во время импорта он создаст новую переменную в области модуля client_b, которой будет присвоено текущее значение db_name из client_a. Вы можете проверить это, как в примере, но если значение изменяется через присваивание в client_a, например, путем вызова initialize_db(), это означает, что ссылка client_a.db_name указывает на новое значение, другие ссылки, например client_b.db_name, по-прежнему указывают на старое значение, назначенное на import, так как мы не переназначили их. Это немного вводит в заблуждение, я обновлю ответ. - person timmwagener; 12.12.2018
comment
Привязка глобальных переменных модуля прямо к самому модулю выглядит супер-круто, но теперь, если клиенты хотят изменить глобальные переменные модуля, они ограничиваются только import db и больше не могут использовать более явный from db import something. Не так уж и круто с точки зрения юзабилити, не правда ли? - person Alex Che; 20.01.2021
comment
Некоторое время успешно использовал это в производственном коде. Недавно начал использовать mypy, которому очень не нравится этот шаблон. Поскольку переменные объявляются и устанавливаются в None при первой инициализации модуля и не получают свои значения до тех пор, пока не будет вызвана функция create, он жалуется, что атрибут не существует и что типы не могут быть объявлены на не- собственные атрибуты. Удаление this избавляет как минимум от второй ошибки. - person pyansharp; 26.03.2021
comment
Согласитесь, я также хотел бы получить какое-то руководство по правильному использованию этого шаблона с mypy / статическим анализом. Разве это невозможно (пока)? Разве такое могло быть когда-то? Возможно ли, но необоснованное усилие? Это просто резкое несоответствие допустимого использования динамических функций и проверки статического типа? - person timmwagener; 26.03.2021

Ответ Стивехи был мне полезен, но упускает важный момент (который, как мне кажется, имел в виду Wisty). Ключевое слово global не требуется, если вы только получаете доступ, но не назначаете переменную в функции.

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

Pylint 1.3.1 в Python 2.7 предписывает НЕ использовать global, если вы не назначаете var.

module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

def readwrite_access():
    global module_var
    module_var = '/dev/hello2'
    connect(module_var)
person Brad Dre    schedule 09.12.2015

Для этого нужно объявить переменную глобальной. Однако глобальная переменная также доступна из вне модуля с помощью module_name.var_name. Добавьте это как первую строку вашего модуля:

global __DBNAME__
person Chinmay Kanchi    schedule 29.12.2009
comment
есть ли способ сделать его доступным для всего модуля, но не доступным для вызова из module_name .__ DBNAME__? - person daveslab; 30.12.2009
comment
Да ... вы можете поместить глобальный оператор внутри своей функции, чтобы сделать его глобальным внутри модуля (внутри этой функции ... вам придется повторять глобальное объявление в каждой функции, которая использует этот глобальный). Например (простите код в комментариях): def initDB(name):\n global __DBNAME__ - person Jarret Hardie; 30.12.2009
comment
Спасибо, Джаррет. К сожалению, когда я пробую это и запускаю dir (mymodule) на консоли, он показывает переменные как доступные, и я могу получить к ним доступ. Я вас неправильно понимаю? - person daveslab; 30.12.2009
comment
Помните, что в Python _DBNAME (одинарное подчеркивание) по соглашению считается частной переменной. Это применяется только частично для классов, но не для открытого кода, но большинство достойных программистов будут рассматривать _var как частный. - person Chinmay Kanchi; 30.12.2009
comment
Это правда, @cgkanchi, но я хотел бы узнать, строго ли это применимо. - person daveslab; 30.12.2009
comment
Поместите все это в класс. Таким образом, по крайней мере, тот, кто хочет получить доступ к частной переменной, должен поработать. - person Chinmay Kanchi; 30.12.2009
comment
Это не подлежащий принудительному исполнению давеслаб. Идея Python состоит в том, что мы все взрослые и что частные и защищенные переменные лучше всего достигаются с помощью контракта и соглашения, что и любой строгий механизм, обеспечиваемый компилятором. - person Jarret Hardie; 30.12.2009

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

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

Ты можешь сделать:

__DB_NAME__ = None

def func():
    if __DB_NAME__:
        connect(__DB_NAME__)
    else:
        connect(Default_value)

но вы не можете повторно назначить __DB_NAME__ внутри функции.

Одно решение:

__DB_NAME__ = [None]

def func():
    if __DB_NAME__[0]:
        connect(__DB_NAME__[0])
    else:
        __DB_NAME__[0] = Default_value

Обратите внимание: я не переназначаю __DB_NAME__, я просто изменяю его содержимое.

person wisty    schedule 30.12.2009
comment
Это неправда. global позволяет вам устанавливать имена уровней модулей. - person dbn; 11.12.2012