web-dev-qa-db-ja.com

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: local variable '__DBNAME__' referenced before assignment

何か案は? このフェローの 推奨に従って、モジュールを使用してシングルトンを設定しようとしています。

182
daveslab

これが何が起こっているかです。

最初に、Pythonが実際に持つ唯一のグローバル変数は、モジュールスコープの変数です。真にグローバルな変数を作成することはできません。特定のスコープで変数を作成するだけです。 (Pythonインタープリター内で変数を作成し、他のモジュールをインポートする場合、変数は最も外側のスコープにあるため、Pythonセッション内でグローバルになります。)

モジュールグローバル変数を作成するために必要なことは、名前に割り当てるだけです。

この1行を含むfoo.pyというファイルを想像してください。

X = 1

それをインポートすると想像してください。

import foo
print(foo.X)  # prints 1

ただし、例のように、モジュールスコープ変数の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.")

ちなみに、この例では、空の文字列以外の文字列値はtrueと評価されるため、実際のデータベース名はtrueと評価されるため、単純なif not __DBNAME__テストで十分です。しかし、0の可能性のある数値を含む可能性のある変数の場合、単にif not variablenameとは言えません。その場合は、None演算子を使用してisを明示的にテストする必要があります。サンプルを変更して、明示的なNoneテストを追加しました。 Noneの明示的なテストは決して間違っていないため、デフォルトで使用します。

最後に、他の人がこのページで指摘したように、2つの先頭のアンダースコアは、変数をモジュールに対して「プライベート」にしたいというPythonに信号を送ります。 import * from mymoduleを実行した場合、Pythonは、名前空間に2つの先行アンダースコアを含む名前をインポートしません。ただし、単純なimport mymoduleを実行してからdir(mymodule)と言うと、リストに「プライベート」変数が表示され、mymodule.__DBNAME__を明示的に参照する場合はPythonは関係ありませんが、参照するだけですそれ。二重の先頭のアンダースコアは、モジュールのユーザーにとって、その名前を自分の値に再バインドしたくないという大きな手がかりです。

Pythonでは、import *ではなく、mymodule.somethingを使用するか、from mymodule import somethingのようなインポートを明示的に行うことにより、結合を最小化し、明示性を最大にすることがベストプラクティスと見なされます。

編集:何らかの理由で、globalキーワードを持たない非常に古いバージョンのPythonでこのようなことをする必要がある場合、簡単な回避策があります。モジュールのグローバル変数を直接設定する代わりに、モジュールのグローバルレベルで可変タイプを使用し、その中に値を保存します。

関数では、グローバル変数名は読み取り専用になります。実際のグローバル変数名を再バインドすることはできません。 (関数内でその変数名に割り当てた場合、関数内のローカル変数名にのみ影響します。)しかし、そのローカル変数名を使用して実際のグローバルオブジェクトにアクセスし、その中にデータを保存できます。

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のような簡単なクラスを使用できます。

208
steveha

モジュール上で明示的にアクセスすることによるモジュールレベル変数への明示的アクセス


要するに:ここで説明する手法は stevehaの答えexcept、変数を明示的にスコープするための人工ヘルパーオブジェクトは作成されません。 代わりに、モジュールオブジェクト自体に変数ポインタが与えられるため、どこからでもアクセスすると明示的なスコープを提供します。 (ローカル関数スコープでの割り当てなど)

selffor現在のインスタンスの代わりに、現在のモジュール

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

モジュールがキャッシュされるため、インポートは1回のみ であるため、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.

追加のボーナスとして、Pythonのポリシーよりも暗黙的よりも優れているため、全体的に非常にPythonicであることがわかりました。

56
timmwagener

スティーブハの答えは私にとっては有益でしたが、重要な点(私は気の利いたと思う点)を省略しています。関数で変数にアクセスするだけで変数を割り当てない場合、globalキーワードは必要ありません。

Globalキーワードなしで変数を割り当てると、Pythonは新しいローカル変数を作成します。モジュール変数の値は関数内に隠されます。関数内でモジュールvarを割り当てるには、globalキーワードを使用します。

Python 2.7のPylint 1.3.1では、変数を割り当てない場合、グローバルを使用しないように強制します。

module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

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

そのためには、変数をグローバルとして宣言する必要があります。ただし、グローバル変数には、module_name.var_nameを使用してoutsideからもアクセスできます。これをモジュールの最初の行として追加します。

global __DBNAME__
6
Chinmay Kanchi