8

関数またはメソッド定義でグローバル変数を誤って (意図せずに) 参照して、これまで何度かトラブルに見舞われました。

私の質問は: Python がグローバル変数を参照できないようにする方法はありますか? または、少なくともグローバル変数を参照していることを警告しますか?

x = 123

def myfunc() :
    print x    # throw a warning or something!!!

これが発生する典型的な状況は、対話型シェルとして IPython を使用していることです。「execfile」を使用して、クラスを定義するスクリプトを実行します。インタープリターでは、クラス変数に直接アクセスして何か役に立つことを行い、それをクラスのメソッドとして追加することにしました。インタープリターにいたとき、クラス変数を参照していました。ただし、メソッドになると「self」を参照する必要があります。これが例です。

class MyClass :

    a = 1
    b = 2

    def add(self) :
        return a+b


m = MyClass()

ここで、インタープリターでスクリプト「execfile('script.py')」を実行し、クラスを調べて「ma * mb」と入力し、決定します。これは便利な方法です。したがって、意図しないコピー/貼り付けエラーが発生するようにコードを変更します。

class MyClass :

    a = 1
    b = 2

    def add(self) :
        return a+b


    def mult(self) :
        return m.a * m.b   # I really meant this to be self.a * self.b

もちろん、これは IPython で実行されますが、以前に定義されたグローバル変数を参照しているため、本当に混乱する可能性があります。

私の典型的な IPython ワークフローを考えると、誰かが提案を持っているかもしれません。

4

2 に答える 2

4

まず、おそらくこれをやりたくないでしょう。Martijn Pieters が指摘しているように、トップレベルの関数やクラスなど、多くのものはグローバルです。

呼び出し不可能なグローバルのみに対してこれをフィルタリングできます。関数、クラス、C 拡張モジュールからインポートした組み込み関数またはメソッドなどは呼び出し可能です。importモジュール (グローバルなものはすべて) を除外することもできます。それでも、たとえば、関数をdef. そのためにある種のホワイトリストを追加できます (これにより、警告なしで使用できるグローバルな「定数」を作成することもできます)。本当に、あなたが思いついたものはせいぜい非常に大雑把なガイドであり、絶対的な警告として扱いたいものではありません.

また、どのように行っても、明示的なアクセス (globalステートメントを使用) ではなく、暗黙的なグローバル アクセスを検出しようとするのは非常に困難になるため、それは重要ではないことを願っています。


ソース レベルでグローバル変数のすべての暗黙的な使用を検出する明確な方法はありません。

ただし、インタープリター内からのリフレクションを使用するのは非常に簡単です。

モジュールのドキュメントにはinspect、さまざまなタイプの標準メンバーを示す素敵なチャートがあります。Python 2.xPython 3.xでは名前が異なるものがあることに注意してください。

この関数は、両方のバージョンのバインドされたメソッド、バインドされていないメソッド、関数、またはコード オブジェクトによってアクセスされるすべてのグローバル名のリストを取得します。

def get_globals(thing):
    thing = getattr(thing, 'im_func', thing)
    thing = getattr(thing, '__func__', thing)
    thing = getattr(thing, 'func_code', thing)
    thing = getattr(thing, '__code__', thing)
    return thing.co_names

呼び出し可能でないもののみを処理したい場合は、それをフィルタリングできます。

def get_callable_globals(thing):
    thing = getattr(thing, 'im_func', thing)
    func_globals = getattr(thing, 'func_globals', {})
    thing = getattr(thing, 'func_code', thing)
    return [name for name in thing.co_names
            if callable(func_globals.get(name))]

これは完璧ではありません (たとえば、関数のグローバルにカスタム組み込みの置換がある場合、適切に検索しません) が、おそらく十分です。


それを使用する簡単な例:

>>> def foo(myparam):
...     myglobal
...     mylocal = 1
>>> print get_globals(foo)
('myglobal',)

そして、非常に簡単importにモジュールを作成し、その呼び出し可能オブジェクトを再帰的にウォークしてget_globals()、それぞれを呼び出すことができます。これは、主要なケース (トップレベル関数、トップレベルおよびネストされたクラスのメソッド) で機能しますが、何に対しても機能しません。動的に定義されます (たとえば、関数内で定義される関数またはクラス)。


CPython だけに関心がある場合は、モジュールを使用してdisモジュール内のすべてのバイトコード、または .pyc ファイル (またはクラスなど) をスキャンし、各操作をログに記録するという別のオプションがありますLOAD_GLOBAL

メソッドに対するこれの主な利点の 1 つinspectは、まだ作成されていない場合でも、コンパイルされた関数を検出できることです。

欠点は、名前を検索する方法がないことです (名前のいくつかがまだ作成されていない場合、どのように存在する可能性がありますか?)。そのため、callable を簡単に除外することはできません。LOAD_GLOBALop を対応する(および関連する) op に接続するなど、凝ったことを試みることはできますがCALL_FUNCTION、かなり複雑になり始めています。


最後に、動的にフックしたい場合は、globalsアクセスするたびに警告するラッパーにいつでも置き換えることができます。例えば:

class GlobalsWrapper(collections.MutableMapping):
    def __init__(self, globaldict):
        self.globaldict = globaldict
    # ... implement at least __setitem__, __delitem__, __iter__, __len__
    # in the obvious way, by delegating to self.globaldict
    def __getitem__(self, key):
        print >>sys.stderr, 'Warning: accessing global "{}"'.format(key)
        return self.globaldict[key]

globals_wrapper = GlobalsWrapper(globals())

繰り返しますが、非呼び出し可能オブジェクトを非常に簡単にフィルタリングできます。

    def __getitem__(self, key):
        value = self.globaldict[key]
        if not callable(value):
            print >>sys.stderr, 'Warning: accessing global "{}"'.format(key)
        return value

print明らかに Python 3 では、ステートメントをprint関数呼び出しに変更する必要があります。

警告の代わりに、非常に簡単に例外を発生させることもできます。または、warningsモジュールの使用を検討することもできます。

これをさまざまな方法でコードにフックできます。最も明白なものは、新しいモジュールごとGlobalsWrapperに、通常ビルドされglobalsた . それが C 拡張モジュールとどのように相互作用するかはわかりませんが、私の推測では、それが機能するか、無害に無視されるかのいずれかであり、おそらくどちらでも問題ありません。唯一の問題は、これがトップレベルのスクリプトに影響を与えないことです。execfileそれが重要な場合は、メイン スクリプトにGlobalsWrapper、またはそのようなものを使用するラッパー スクリプトを作成できます。

于 2013-05-23T18:53:00.123 に答える