Как создать всплывающие переменные в 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", на которую ссылается перед назначением...

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

Ответ 1

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

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

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

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

X = 1

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

import foo
print(foo.X)  # prints 1

Однако предположим, что вы хотите использовать одну из ваших переменных области модуля как глобальную внутри функции, как в вашем примере. По умолчанию Python предполагает, что переменные функции являются локальными. Вы просто добавляете объявление 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__ достаточен, потому что любое строковое значение, отличное от пустой строки, будет оценивать true, поэтому любое фактическое имя базы данных будет оценивать true. Но для переменных, которые могут содержать числовое значение, которое может быть 0, вы не можете просто сказать if not variablename; в этом случае вы должны явно протестировать None с помощью оператора is. Я изменил пример, чтобы добавить явный тест None. Явный тест для None никогда не ошибается, поэтому я по умолчанию его использовал.

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

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

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

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

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

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

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

A 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 в приведенном выше.

Ответ 2

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


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

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

# 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 лучше, чем неявную.

Ответ 3

Ответ Steveha был полезен для меня, но опускает важный момент (тот, который, как мне кажется, был wisty). Глобальное ключевое слово не обязательно, если вы только получаете доступ, но не назначаете переменную в функции.

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

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

module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

def readwrite_access():
    global module_var
    module_var = '/dev/hello2'
    connect(module_var)

Ответ 4

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

global __DBNAME__

Ответ 5

Ты падаешь на тонкую причуду. Вы не можете повторно назначать переменные уровня модуля внутри функции 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__, я просто изменяю его содержимое.