7

標準ライブラリのいくつかのクラスを変更して、そのモジュールの他のクラスが使用するグローバルとは異なるセットを使用したいと考えています。

この例は単なる例です。

# module_a.py

my_global = []

class A:
    def __init__(self):
        my_global.append(self)

class B:
    def __init__(self):
        my_global.append(self)

この例では、 のインスタンスをA経由で作成すると、 という名前のオブジェクトA()が呼び出されます。しかし今、私は新しいモジュールを作成し、それにインポートし、元の定義されたモジュールからではなく、インポートされたモジュールから使用したいと考えています。appendmy_globalBBmy_globalmy_globalB

# module_b.py

from module_a import B

my_global = []

関連している

私は自分の問題を説明するのに苦労しています。これは、実際にはまったく異なることを尋ねた私の以前の試みです:

Update0

  • 上記の例は、私が達成しようとしていることを説明するためのものです。
  • クラスには変数スコープがないため (たとえば C++ とは異なり)、グローバル マッピングへの参照はクラスに格納されず、定義時にすべての関数にアタッチされると思います。

アップデート1

標準ライブラリから例が要求されました。

モジュール内のクラスの多く (おそらくすべて?) は、hereおよびhereで定義された、、およびthreadingなどのグローバルを使用します。そのモジュール内のすべてのクラスを変更せずに、これらのグローバルを変更することはできません。_allocate_lockget_ident_active

4

7 に答える 7

5

モジュールの他のすべてのユーザーに影響を与えずにグローバルを変更することはできませんが、できることは、モジュール全体のプライベート コピーを作成することです。

に慣れているとsys.modules思います。そこからモジュールを削除すると、Python はそれがインポートされたことを忘れますが、それを参照する古いオブジェクトは引き続きインポートされます。再度インポートすると、モジュールの新しいコピーが作成されます。

あなたの問題に対するハックな解決策は、次のようなものになる可能性があります。

import sys
import threading

# Remove the original module, but keep it around
main_threading = sys.modules.pop('threading')

# Get a private copy of the module
import threading as private_threading

# Cover up evidence by restoring the original
sys.modules['threading'] = main_threading

# Modify the private copy
private_threading._allocate_lock = my_allocate_lock()

そして今、グローバルは!private_threading.Lockから完全に分離されています。threading.Lock

言うまでもなく、このモジュールはこれを念頭に置いて作成されたものではなく、特にthreading問題が発生する可能性のあるシステム モジュールでは作成されていません。たとえば、threading._active実行中のすべてのスレッドが含まれていると想定されていますが、このソリューションでは、どちらもすべてを含むことはありません_active。コードは靴下を食べたり、家に火をつけたりすることもあります。厳密にテストしてください。

于 2011-10-09T18:42:03.647 に答える
1

「そのモジュール内のすべてのクラスを変更せずに、これらのグローバルを変更することはできません。」globalそれが問題の根源であり、一般的な変数の問題をよく説明しています。globalsスレッド化で を使用すると、そのクラスがそれらのグローバル オブジェクトに結び付けられます。

モジュールから個々のクラス内でグローバル変数を使用するたびに何かをジェリーリグで見つけてモンキー パッチを適用するまでに、コードを自分で使用するために再実装するよりも先に進んでいますか?

あなたの状況で「かもしれない」唯一の回避策は、mockのようなものです。Mock のパッチ デコレータ/コンテキスト マネージャ (または同様のもの) を使用して、特定のオブジェクトの存続期間のグローバル変数を交換できます。単体テストの非常に制御されたコンテキスト内ではうまく機能しますが、それ以外の状況ではお勧めしません。自分のニーズに合うようにコードを再実装することだけを考えます。

于 2011-10-07T02:20:22.037 に答える
1

さて、これはその方法を示す概念実証です。1 レベルの深さしかないことに注意してください。プロパティとネストされた関数は調整されません。これを実装し、これをより堅牢にするために、各関数の globals() を、置き換える必要がある globals() と比較し、それらが同じ場合にのみ置換を行う必要があります。

def migrate_class(cls, globals):
    """Recreates a class substituting the passed-in globals for the
    globals already in the existing class.  This proof-of-concept
    version only goes one-level deep (i.e. properties and other nested
    functions are not changed)."""
    name = cls.__name__
    bases = cls.__bases__
    new_dict = dict()
    if hasattr(cls, '__slots__'):
        new_dict['__slots__'] = cls.__slots__
        for name in cls.__slots__:
            if hasattr(cls, name):
                attr = getattr(cls, name)
                if callable(attr):
                    closure = attr.__closure__
                    defaults = attr.__defaults__
                    func_code = attr.__code__
                    attr = FunctionType(func_code, globals)
                new_dict[name] = attr
    if hasattr(cls, '__dict__'):
        od = getattr(cls, '__dict__')
        for name, attr in od.items():
            if callable(attr):
                closure = attr.__closure__
                defaults = attr.__defaults__
                kwdefaults = attr.__kwdefaults__
                func_code = attr.__code__
                attr = FunctionType(func_code, globals, name, defaults, closure)
                if kwdefaults:
                    attr.__kwdefaults__ = kwdefaults
            new_dict[name] = attr
    return type(name, bases, new_dict)

この演習を行った後、なぜこれを行う必要があるのか​​非常に興味があります

于 2011-10-06T23:09:13.150 に答える
0

あなたが十分によく知っていると確信しているように、グローバルはまさにこの理由で悪いです。

私は自分のモジュールで A と B を (おそらくサブクラス化して) 再実装し、my_global へのすべての参照を A と B への注入された依存関係 (ここではレジストリと呼びます) に置き換えます。

class A(orig.A):

    def __init__(self, registry):
        self.registry = registry
        self.registry.append(self)

    # more updated methods

A のすべてのインスタンスを自分で作成している場合は、ほぼ完了です。新しい init パラメータを隠すファクトリを作成したい場合があります。

my_registry = []
def A_in_my_registry():
    return A(my_registry)

外部コードが orig.A インスタンスを作成し、代わりに新しい A インスタンスが必要な場合は、外部コードがファクトリでカスタマイズ可能であることを期待する必要があります。そうでない場合は、外部クラスから派生させ、代わりに (新しく注入された) A ファクトリを使用するように更新します。....そして、これらの更新されたクラスを作成するために、すすぎを繰り返します。外部コードの複雑さによっては、これが退屈でほとんど不可能なことがあることは理解していますが、ほとんどの標準ライブラリは非常にフラットです。

--

編集: モンキー パッチ std lib コード。

std ライブラリにモンキー パッチを適用することを気にしない場合は、元のクラスを変更して、元のグローバルにデフォルト設定されるリダイレクト レベルで動作するようにすることもできますが、インスタンスごとにカスタマイズ可能です。

import orig

class A(orig.A):

    def __init__(self, registry=orig.my_globals):
        self.registry = registry
        self.registry.append(self)

    # more updated methods

orig.A = A

以前と同様に、非「標準グローバル」を使用する A の作成を制御する必要がありますが、十分に早い段階でパッチを適用する限り、さまざまな A クラスが存在することはありません。

于 2011-10-07T22:13:11.243 に答える
0

Python 3 を使用している場合は、B をサブクラス化し、次のようにメソッドの__globals__属性を再定義できます。__init__

from module_a import B

function = type(lambda: 0)  # similar to 'from types import FunctionType as function', but faster
my_global = []


class My_B (B):
    __init__ = function(B.__init__.__code__, globals(), '__init__',  B.__init__.__defaults__, B.__init__.__closure__)
于 2011-10-11T12:24:17.143 に答える
-4

グローバルはめったに良い考えではありません。

暗黙的な変数が良い考えになることはめったにありません。

暗黙的に使用されるグローバルは、「めったに良くない」と簡単に指摘できます。

A.__init__()さらに、クラス全体に存在する不思議なコレクションを更新するような「クラスレベル」のことはしたくありません。それはしばしば悪い考えです。

暗黙的なクラスレベルのコレクションを台無しにするのではなく、(1) インスタンスを作成し、(b) 明示的なコレクションを更新するFactoryが必要です。module_aAB

module_bその後、別のコレクションを除いて、このファクトリを で使用できます。

これにより、暗黙的な依存関係が公開されるため、テスト容易性が向上します。

module_a.py

class Factory( object ):
    def __init__( self, collection ):
        self.collection= collection
    def make( self, name, *args, **kw ):
        obj= eval( name )( *args, **kw )
        self.collection.append( obj )
        return obj

module_collection = []
factory= Factory( module_collection )

module_b.py

module_collection = []
factory = module_a.Factory( module_collection )

これで、クライアントはこれを行うことができます

import module_b
a = module_b.factory.make( "A" )
b = module_b.factory.make( "B" )
print( module_b.module_collection )

ファクトリを「呼び出し可能」にすることで、API をもう少し流暢にすることができます__call__( make.

ポイントは、ファクトリ クラスを介してコレクションを明示的にすることです。

于 2011-09-21T09:54:52.143 に答える