Как сделать кросс-модульную переменную?

122

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

Переменная (давайте будем оригинальными и назовем ее «foo») не обязательно должна быть по-настоящему глобальной, в том смысле, что если я изменю foo в одном модуле, он обновится в других. Было бы хорошо, если бы я мог установить foo перед импортом других модулей, и тогда они увидели бы для него то же значение.

Дэн Хомерик
источник

Ответы:

114

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

a.py содержит

print foo

b.py содержит

import __builtin__
__builtin__.foo = 1
import a

В результате печатается «1».

Edit:__builtin__ модуль доступен как локальный символ __builtins__- вот причина расхождения между двумя этими ответами. Также обратите внимание, что __builtin__он был переименован builtinsв python3.

Курт Хагенлохер
источник
2
Есть ли причина, по которой вам не нравится эта ситуация?
Software Enthusiastic
31
Во-первых, это разбивает ожидания людей, когда они читают код. «Что это за символ« foo »? Почему я не вижу, где он определен?»
Курт Хагенлохер,
9
Он также может нанести серьезный ущерб, если будущая версия Python начнет использовать выбранное вами имя как фактическую встроенную команду.
интуитивно понятно
4
Это хорошее решение для таких вещей, как совместное использование db-соединения с импортированными модулями. В качестве проверки работоспособности я убеждаюсь, что импортированный модуль утверждает hasattr(__builtin__, "foo").
Майк Эллис
4
Для всех, кто читает этот ответ: НЕ! ДЕЛАТЬ ! ЭТОТ ! На самом деле не надо.
bruno desthuilliers
161

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

a.py:

var = 1

b.py:

import a
print a.var
import c
print a.var

c.py:

import a
a.var = 2

Тест:

$ python b.py
# -> 1 2

Пример из реальной жизни : global_settings.py Django (хотя в приложениях Django настройки используются путем импорта объекта django.conf.settings ).

JFS
источник
3
Лучше, потому что это позволяет избежать возможных конфликтов пространств имен
bgw
Что, если модуль, который вы импортируете, в этом случае a.pyсодержит main()? Это имеет значение?
sedeh
4
@sedeh: нет. Если a.py также запускается как сценарий, используйте if __name__=="__main__"в нем охрану, чтобы избежать запуска неожиданного кода при импорте.
jfs
6
В реальном мире с этим решением нужно быть осторожнее. Если программист выбирает вашу «глобальную» переменную с помощью «from a import var» (попробуйте этот вариант в c.py), он получит копию переменной во время импорта.
Пол Уипп
1
@PaulWhipp: неправильно (подсказка: используйте id()для проверки личности)
jfs
25

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

Когда есть только один такой модуль, я называю его «g». В нем я назначаю значения по умолчанию для каждой переменной, которую собираюсь рассматривать как глобальную. В каждом модуле, который использует любой из них, я не использую «from g import var», так как это приводит только к локальной переменной, которая инициализируется из g только во время импорта. Я делаю большинство ссылок в форме g.var и "g." служит постоянным напоминанием о том, что я имею дело с переменной, потенциально доступной для других модулей.

Если значение такой глобальной переменной должно часто использоваться в какой-либо функции в модуле, тогда эта функция может сделать локальную копию: var = g.var. Однако важно понимать, что присвоения переменной var являются локальными, а глобальная g.var не может быть обновлена ​​без явной ссылки на g.var в назначении.

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

По-прежнему возможно назначить, скажем, gx, когда x еще не определен в g, и тогда другой модуль может получить доступ к gx. Однако, хотя интерпретатор разрешает это, этот подход не так прозрачен, и я избегаю Это. По-прежнему существует возможность случайного создания новой переменной в g в результате опечатки в имени переменной для присвоения. Иногда изучение dir (g) полезно для обнаружения каких-либо неожиданных имен, которые могли возникнуть в результате такой случайности.

Дэвид Вандершель
источник
7
Это интересное наблюдение решило мою проблему: «Я не использую« from g import var », так как это приводит только к локальной переменной, которая инициализируется из g только во время импорта». Кажется разумным предположить, что "from..import" - это то же самое, что "import", но это неверно.
Curtis Yallop
24

Определите модуль (назовите его globalbaz) и определите в нем переменные. Все модули, использующие этот «псевдоглобальный», должны импортировать модуль «globalbaz» и обращаться к нему, используя «globalbaz.var_name».

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

Для пояснения, globalbaz.py выглядит примерно так:

var_name = "my_useful_string"
hayalci
источник
9

Вы можете передать глобальные объекты одного модуля другому:

В модуле A:

import module_b
my_var=2
module_b.do_something_with_my_globals(globals())
print my_var

В модуле B:

def do_something_with_my_globals(glob): # glob is simply a dict.
    glob["my_var"]=3
user394430
источник
7

Глобальные переменные обычно плохая идея, но вы можете сделать это, присвоив __builtins__:

__builtins__.foo = 'something'
print foo

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

# my_globals.py
foo = 'something'

Тогда вы можете использовать это откуда угодно:

import my_globals
print my_globals.foo

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

спекулянт
источник
3
__builtins__это особенность CPython, вам действительно не следует ее использовать - лучше используйте __builtin__(или builtinsв Python3), как показывает принятый ответ
Тобиас Кинцлер
5

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

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

созерцаемое
источник
3

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

Циклический импорт может нарушить работу модуля.

Например:

first.py

import second
var = 1

second.py

import first
print(first.var)  # will throw an error because the order of execution happens before var gets declared.

main.py

import first

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

Джонатан
источник
1

Это похоже на изменение __builtin__пространства имен. Сделать это:

import __builtin__
__builtin__.foo = 'some-value'

Не используйте __builtins__напрямую (обратите внимание на лишние «s») - очевидно, это может быть словарь или модуль. Благодаря ΤΖΩΤΖΙΟΥ за указание на это можно найти больше здесь .

Теперь fooдоступен для использования везде.

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

Назначение для него должно быть выполнено, как указано выше, просто установка foo = 'some-other-value'установит его только в текущем пространстве имен.

awatts
источник
1
Я помню (из comp.lang.python), что следует избегать прямого использования встроенных функций ; вместо этого импортируйте встроенную команду и используйте ее, как предложил Курт Хагенлохер.
tzot
1

Я использую это для пары встроенных примитивных функций, которых, как мне казалось, действительно не хватало. Одним из примеров является функция поиска, которая имеет ту же семантику использования, что и filter, map, reduce.

def builtin_find(f, x, d=None):
    for i in x:
        if f(i):
            return i
    return d

import __builtin__
__builtin__.find = builtin_find

После запуска (например, путем импорта рядом с вашей точкой входа) все ваши модули могут использовать find (), как если бы, очевидно, он был встроен.

find(lambda i: i < 0, [1, 3, 0, -5, -10])  # Yields -5, the first negative.

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

Брайан Арсуага
источник
1

Я мог бы получить изменяемые (или изменяемые ) переменные кросс-модуля , используя словарь:

# in myapp.__init__
Timeouts = {} # cross-modules global mutable variables for testing purpose
Timeouts['WAIT_APP_UP_IN_SECONDS'] = 60

# in myapp.mod1
from myapp import Timeouts

def wait_app_up(project_name, port):
    # wait for app until Timeouts['WAIT_APP_UP_IN_SECONDS']
    # ...

# in myapp.test.test_mod1
from myapp import Timeouts

def test_wait_app_up_fail(self):
    timeout_bak = Timeouts['WAIT_APP_UP_IN_SECONDS']
    Timeouts['WAIT_APP_UP_IN_SECONDS'] = 3
    with self.assertRaises(hlp.TimeoutException) as cm:
        wait_app_up(PROJECT_NAME, PROJECT_PORT)
    self.assertEqual("Timeout while waiting for App to start", str(cm.exception))
    Timeouts['WAIT_JENKINS_UP_TIMEOUT_IN_SECONDS'] = timeout_bak

При запуске test_wait_app_up_failфактическая продолжительность тайм-аута составляет 3 секунды.

foudfou
источник
1

Мне было интересно, можно ли избежать некоторых недостатков использования глобальных переменных (см., Например, http://wiki.c2.com/?GlobalVariablesAreBad ), используя пространство имен класса, а не пространство имен global / module для передачи значений переменных. , Следующий код указывает на то, что эти два метода практически идентичны. Как поясняется ниже, использование пространств имен классов дает небольшое преимущество.

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

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

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

source.py

import wall
def sourcefn():
    msg = 'Hello world!'
    wall.msg = msg
    wall.router.msg = msg

Этот модуль импортирует стену и определяет одну функцию, sourcefnкоторая определяет сообщение и передает его с помощью двух разных механизмов, одного через глобальные переменные, а другого через функцию маршрутизатора. Обратите внимание, что переменные wall.msgи wall.router.messageопределены здесь впервые в своих соответствующих пространствах имен.

dest.py

import wall
def destfn():

    if hasattr(wall, 'msg'):
        print 'global: ' + wall.msg
        del wall.msg
    else:
        print 'global: ' + 'no message'

    if hasattr(wall.router, 'msg'):
        print 'router: ' + wall.router.msg
        del wall.router.msg
    else:
        print 'router: ' + 'no message'

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

main.py

import source, dest

source.sourcefn()

dest.destfn() # variables deleted after this call
dest.destfn()

Этот модуль последовательно вызывает ранее определенные функции. После первого вызова dest.destfnпеременных wall.msgи wall.router.msgбольше не существует.

Вывод программы:

global: Привет, мир!
маршрутизатор: Привет, мир!
global: нет сообщений,
маршрутизатор: нет сообщений

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

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

robertofbaycot
источник