Як зробити перехресну модульну змінну?


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
Будь-яка причина, що вам не подобається ця ситуація?
Ентузіазм програмним забезпеченням

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).


3
Краще, тому що це дозволяє уникнути можливих конфліктів у просторі імен
bgw

Що робити, якщо модуль, який ви імпортуєте, у цьому випадку a.pyмістить main()? Це важливо?
sedeh

4
@sedeh: ні. Якщо a.py також виконується як сценарій, використовуйте if __name__=="__main__"в ньому захист, щоб уникнути запуску несподіваного коду при імпорті.
jfs

6
У реальному світі вам слід бути трохи обережними з цим рішенням. Якщо програміст підбирає вашу "глобальну" змінну за допомогою "з імпорту var", (спробуйте цю варіацію в c.py), вони отримують копію змінної під час імпорту.
Пол Віпп

1
@PaulWhipp: неправильно (підказка: використовувати id()для перевірки особи)
jfs

25

Я вважаю, що існує багато обставин, в яких це має сенс, і це спрощує програмування, щоб мати деякі глобальні точки, відомі в декількох (щільно зв'язаних) модулях. У цьому дусі я хотів би трохи детальніше розібратися над ідеєю створення модуля глобалів, який імпортується тими модулями, які потребують посилання на них.

Коли є лише один такий модуль, я називаю його «g». У ній я призначаю значення за замовчуванням для кожної змінної, яку я маю вважати глобальною. У кожному модулі, який використовує будь-який з них, я не використовую "з g імпорту var", оскільки це призводить лише до локальної змінної, яка ініціалізується з g лише під час імпорту. Я роблю більшість посилань у формі g.var та "g". служить постійним нагадуванням про те, що я маю справу зі змінною, яка є потенційно доступною для інших модулів.

Якщо значення такої глобальної змінної потрібно часто використовувати в якійсь функції в модулі, то ця функція може зробити локальну копію: var = g.var. Однак важливо усвідомити, що присвоєння var є локальними, а глобальний g.var не може бути оновлений без прямого посилання g.var у призначенні.

Зауважте, що ви також можете мати декілька таких глобальних модулів, якими поділяються різні підмножини ваших модулів, щоб все було трохи жорсткіше контролювати. Причина, що я використовую короткі імена для моїх глобальних модулів, - це уникати занадто захаращення коду над появами їх. Маючи трохи досвіду, вони стають досить мнемічними лише з 1 або 2 символами.

Ще можна зробити присвоєння, скажімо, gx, коли x ще не було визначено в g, а інший модуль може отримати доступ до gx. Однак, хоча інтерпретатор дозволяє, цей підхід не такий прозорий, і я уникаю цього це. Досі існує можливість випадкового створення нової змінної в g в результаті помилки в назві змінної для призначення. Іноді експертиза dir (g) корисна для виявлення будь-яких імен несподіванок, які могли виникнути внаслідок такої аварії.


7
Це цікаве спостереження вирішило мою проблему: "Я не використовую" з g import var ", оскільки це призводить лише до локальної змінної, яка ініціалізується з g лише під час імпорту." Здається розумним припустити, що "з..імпорту" - це те саме, що "імпорт", але це неправда.
Кертіс Яллоп

24

Визначте модуль (назвіть його "globalbaz") і вкажіть змінні всередині нього. Усі модулі, що використовують цей "псевдоглобал", повинні імпортувати модуль "globalbaz" і посилатися на нього за допомогою "globalbaz.var_name"

Це працює незалежно від місця зміни, ви можете змінити змінну до або після імпорту. Імпортний модуль буде використовувати останнє значення. (Я тестував це на прикладі іграшки)

Для уточнення, globalbaz.py виглядає приблизно так:

var_name = "my_useful_string"

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

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), як свідчить прийнята відповідь
Tobias Kienzler

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'встановить його лише в поточному просторі імен.


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

1

Я використовую це для пари вбудованих примітивних функцій, які, як мені здалося, справді відсутні. Одним із прикладів є функція пошуку, яка має ту саму семантику використання, як фільтр, карта, зменшення.

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 секунди.


1

Мені було цікаво, чи можна було б уникнути деяких недоліків використання глобальних змінних (див., Наприклад, http://wiki.c2.com/?GlobalVariablesAreBad ), використовуючи простір імен класів, а не глобальний / ім'я модуля для передачі значень змінних . Наступний код вказує, що два способи по суті однакові. Існує невелика перевага у використанні просторів імен класів, як пояснено нижче.

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

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

Я називаю цей модуль "стіною", оскільки він використовується для відмови від змінних. Він буде діяти як простір для тимчасового визначення глобальних змінних та загальнокласних атрибутів порожнього класу "маршрутизатор".

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 тощо, або шляхом визначення декількох класів типу маршрутизатора в одному файлі. Останній трохи охайніший, тому, можливо, являє собою граничну перевагу для використання механізму змінної класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.