インポートされたモジュールのグローバル変数の可視性


111

Pythonスクリプトでモジュールをインポートする壁に少し遭遇しました。私はエラーを説明するために最善を尽くし、なぜそれに遭遇するのか、なぜこの特定のアプローチを結んで問題を解決するのか(これについては後で説明します):

この補助モジュールがインポートされる名前空間で定義されたエンティティを参照するユーティリティ関数/クラスをいくつか定義したモジュールがあるとします(「a」をそのようなエンティティにします)。

module1:

def f():
    print a

そして、「a」が定義されているメインプログラムがあり、そこにこれらのユーティリティをインポートします。

import module1
a=3
module1.f()

プログラムを実行すると、次のエラーが発生します。

Traceback (most recent call last):
  File "Z:\Python\main.py", line 10, in <module>
    module1.f()
  File "Z:\Python\module1.py", line 3, in f
    print a
NameError: global name 'a' is not defined

同様の質問が過去(2日前、d'uh)に尋ねられ、いくつかの解決策が提案されましたが、これらが私の要件に実際に合うとは思いません。ここに私の特定のコンテキストがあります:

MySQLデータベースサーバーに接続し、データをGUIで表示/変更するPythonプログラムを作成しようとしています。わかりやすくするために、補助/ユーティリティのMySQL関連関数の束を別のファイルで定義しました。ただし、これらの変数にはすべて、最初にユーティリティモジュール内で定義した共通の変数があり、これはMySQLdbモジュールのカーソルオブジェクトです。カーソルオブジェクト(dbサーバーとの通信に使用される)はメインモジュールで定義する必要があることを後で理解しました。これにより、メインモジュールとそれにインポートされるすべてのオブジェクトがそのオブジェクトにアクセスできるようになります。

最終結果は次のようになります:

Utilities_module.py:

def utility_1(args):
    code which references a variable named "cur"
def utility_n(args):
    etcetera

そして私のメインモジュール:

program.py:

import MySQLdb, Tkinter
db=MySQLdb.connect(#blahblah) ; cur=db.cursor()  #cur is defined!
from utilities_module import *

そして、ユーティリティ関数のいずれかを呼び出そうとすると、前述の「グローバル名が定義されていません」エラーがトリガーされます。

特に、次のようなユーティリティファイルに「from program import cur」ステートメントを含めることを提案しました。

Utilities_module.py:

from program import cur
#rest of function definitions

program.py:

import Tkinter, MySQLdb
db=MySQLdb.connect(#blahblah) ; cur=db.cursor()  #cur is defined!
from utilities_module import *

しかし、それは循環インポートまたはそのようなものであり、最終的にはクラッシュします。だから私の質問は:

メインモジュールで定義された「cur」オブジェクトを、インポートされた補助関数から見えるようにするにはどうすればよいですか。

解決策が他の場所に投稿されている場合は、お時間をいただきありがとうございます。自分で答えを見つけることができず、本にこれ以上のトリックはありません。


1
更新に基づいて:とにかく、単一の共有カーソルは必要ありません。単一の共有接続、はい、ただしカーソルは安価であり、複数のカーソルを同時に有効にしておくことには多くの理由があります(たとえば、代わりにfetch_all2つのリストを繰り返し処理する代わりに、ロックステップで2つのカーソルを繰り返し処理することができます)または、データベースを使用して、競合なしに2つの異なるスレッド/グリーンレット/コールバックチェーン/何でも持つことができます)。
abarnert 2013

とにかく、あなたが共有したいものは何でも、私はここで答えが移動することだと思いますdb(そしてcur、あなたが主張する場合)の両方がその別のモジュールへprogramutilities_moduleから、それをインポートします。そうすれば、循環依存関係(プログラムがインポートするモジュールからプログラムをインポートすること)とそれに伴う混乱が発生しなくなります。
abarnert 2013

回答:


242

Pythonのグローバルは、モジュール全体に対してではなく、モジュールに対してグローバルです。(たとえば、Cでは、明示的に指定しない限り、グローバルはすべての実装ファイルで同じであるため、多くの人がこれに混乱していstaticます。)

これを解決するには、実際のユースケースに応じてさまざまな方法があります。


この道を進む前に、これが本当にグローバルである必要があるかどうか自問してください。たぶん、あなたは本当にfフリーな関数ではなく、インスタンスメソッドとしてのクラスを本当に望んでいるのでしょうか?次に、このようなことをすることができます:

import module1
thingy1 = module1.Thingy(a=3)
thingy1.f()

本当にグローバルが必要だが、それがで使用されるためだけにあるmodule1場合は、そのモジュールに設定します。

import module1
module1.a=3
module1.f()

一方、a多くのモジュールで共有されている場合は、別の場所に配置して、全員にインポートしてもらいます。

import shared_stuff
import module1
shared_stuff.a = 3
module1.f()

…そして、module1.pyで:

import shared_stuff
def f():
    print shared_stuff.a

from変数が定数であることが意図されていない限り、インポートを使用しないでください。インポート時に参照されたものに初期化されfrom shared_stuff import aた新しいa変数が作成shared_stuff.aされ、この新しいa変数はへの割り当ての影響を受けませんshared_stuff.a


または、ビルトインのように、どこでも本当にグローバルであることが本当に必要な場合は、ビルトインモジュールに追加します。正確な詳細は、Python 2.xと3.xで異なります。3.xでは、次のように機能します。

import builtins
import module1
builtins.a = 3
module1.f()

12
素敵で包括的な。
キンダル2013

ご回答有難うございます。メインモジュールで定義された変数が実際にはグローバルではないという事実を克服することはできませんが、私はimport shared_stuffアプローチを試します-プログラム内のどこからでも、誰でも本当に名前にアクセスできるようにする方法はありません?
Nubarke 2013

1
何かを持つことは、「どんなプログラムからでも、誰もがアクセスできる」ことは、Python禅に反することになります。具体的には、「明示的なものは暗黙的なものより優れています」。Pythonは非常によく考えられたooデザインを持っているので、うまく使用すれば、もう一度globalキーワードを使用する必要なく、Pythonのキャリアの残りの部分をなんとかすることができます。
Bi Rico

1
更新:ありがとう!shared_stuffのアプローチは問題なく機能しましたが、これで他の問題を回避できると思います。
Nubarke 2013

1
@DanielArmengod:本当に必要な場合に備えて、組み込みの回答を追加しました。(さらに詳細が必要な場合は、SOを検索してください。ビルトインに適切にコンテンツを追加する方法については、少なくとも2つの質問があります。)しかし、Bi Ricoが言うように、ほとんどの場合、本当に必要としないか、必要としません。
abarnert 2013

8

回避策として、次のように、外部層で環境変数を設定することを検討できます。

main.py:

import os
os.environ['MYVAL'] = str(myintvariable)

mymodule.py:

import os

myval = None
if 'MYVAL' in os.environ:
    myval = os.environ['MYVAL']

追加の予防策として、MYVALがモジュール内で定義されていない場合に対処します。


5

関数は、それが定義されているモジュールのグローバルを使用します。a = 3たとえば、を設定する代わりに、を設定する必要がありますmodule1.a = 3。したがって、でcurグローバルとして使用できるようにするにはutilities_module、次のように設定します。utilities_module.curます。

より良いソリューション:グローバルを使用しないでください。必要な変数を必要な関数に渡すか、クラスを作成してすべてのデータをまとめ、インスタンスの初期化時に渡します。


「import module1」と書く代わりに、ユーザーが「from module1 import f」と書いた場合、fはmain.pyのグローバル名前空間に入ります。f()を使用すると、main.pyでa = 3とf(関数定義)がどちらもmainのグローバル名前空間にあるためです。これは解決策ですか?私が間違っている場合は、この件に関する任意の記事を教えていただけますか
変数

上記のアプローチを使用し、グローバルを使用しているクラスをインスタンス化するときに引数としてグローバルを渡しました。これは問題ありませんでしたが、しばらくしてからコードのSonarqubeコードチェックをアクティブにし、関数の引数が多すぎると不満を訴えました。したがって、別のソリューションを探す必要がありました。次に、環境変数を必要とする各モジュールによって読み取られる環境変数を使用します。実際にはOOPに準拠していませんが、それだけです。これは、コードの実行中にグローバルが変更されない場合にのみ機能します。
rimetnac

5

この投稿は、私が遭遇したPythonの動作の観察にすぎません。上で読んだアドバイスが、私が以下で行ったのと同じことをしても効果がないかもしれません。

すなわち、私はグローバル/共有変数を含むモジュールを持っています(上で示唆されたように):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

それから私は共有のものをインポートするメインモジュールを持っていました:

import sharedstuff as shared

そして、これらのアレイに実際にデータを追加した他のいくつかのモジュール。これらはメインモジュールによって呼び出されます。これらの他のモジュールを終了すると、配列が読み込まれていることがはっきりとわかります。しかし、それらをメインモジュールで読み返すと、空でした。これは私にはかなり奇妙でした(まあ、私はPythonが初めてです)。ただし、メインモジュールのsharedstuff.pyのインポート方法を次のように変更すると、

from globals import *

機能しました(アレイにデータが追加されました)。

言ってるだけ'


2

この特定の問題に対する最も簡単な解決策は、カーソルをモジュールのグローバル変数に格納する別の関数をモジュール内に追加することでした。次に、他のすべての関数もそれを使用できます。

module1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

def method(some, args):
    global cursor
    do_stuff(cursor, some, args)

メインプログラム:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()

2

グローバルはモジュール固有であるため、インポートされたすべてのモジュールに次の関数を追加して、それを使用できます。

  • それらのグローバルとして(ディクショナリ形式の)特異変数を追加する
  • メインモジュールグローバルをそれに転送します。

addglobals = lambda x:globals()。update(x)

次に、現在のグローバルを渡す必要があるのは次のとおりです。

インポートモジュール

module.addglobals(globals())


1

上記の回答ではそれを見たことがないのでglobal_dict、呼び出しモジュールのグローバルを必要とする関数に引数を追加し、呼び出し時に関数にdictを渡すという簡単な回避策を追加すると思いました。例えば:

# external_module
def imported_function(global_dict=None):
    print(global_dict["a"])


# calling_module
a = 12
from external_module import imported_function
imported_function(global_dict=globals())

>>> 12

0

これを行うOOPの方法は、モジュールを一連のバインドされていないメソッドではなくクラスにすることです。次に__init__、またはsetterメソッドを使用して、モジュールメソッドで使用するために呼び出し元からの変数を設定できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.