2017-11-04 7 views
6

は明示的にインポートすることなく、かつ(@something.some_decoratorを)それをプレフィックスすることなく、特定のメソッドのデコレータ利用できるようにするようにクラスを変更することが可能です:明示的にインポートすることなくクラス内で新しいデコレータを使用できるようにするにはどうすればよいですか?

class SomeClass: 

    @some_decorator 
    def some_method(self): 
     pass 

私はこれが可能だとは思いませんそれはあまりにも遅く適用されるので、クラスデコレータで。より有望なオプションは、メタクラスを使用しているようですが、わかりません。some_decoratorSomeClassという名前空間に導入する必要があります。

staticmethodclassmethodが組み込みであることを指摘してくれた@MartijnPietersに感謝します。私はそれらがtypeの機械の一部であると期待していました。

明確にするために、明示的なユースケースはありませんが、これがすべて可能かどうか不思議です。

追加、質問に答えました。私が単にデコレータをローカルにインポートしたり定義したりすることを念頭に置いていた元の理由は、特定のコンテナ属性がオブジェクト上で初期化されている場合にのみ機能するデコレータを定義していました。デコレータの可用性。属性が存在するかどうか、デコレータ内で属性を初期化していないかどうかを確認しましたが、ここではそれほど悪いことはありません。

+1

これを更新していただきありがとうございます。以前の質問より*が*はっきりしています。 –

答えて

7

はい、Python 3ではmetaclass __prepare__ hookを使用できます。マッピングを返すことが期待され、それがクラス本体のローカル名前空間の基礎を形成する:上記を実行する

def some_decorator(f): 
    print(f'Decorating {f.__name__}') 
    return f 

class meta(type): 
    @classmethod 
    def __prepare__(mcls, name, bases, **kw): 
     return {'some_decorator': some_decorator} 

class SomeClass(metaclass=meta): 
    @some_decorator 
    def some_method(self): 
     pass 

は、あなたがこれを使用するべきではありません

Decorating some_method 

しかしを生成しますZen of Python状態:明示的暗黙よりも暗い、クラスに魔法の名前を導入するとは簡単に混乱とバグにつながる可能性があります。メタクラスをインポートすることは、デコレータをインポートすることと変わりありません。ある名前を別の名前に置き換えました。

クラスデコレータは、クラス本体が作成された後でもクラスのメソッドに他のデコレータを適用できます。 @decoratorの構文はname = decorator(decorated_object)の構文砂糖だけですが、後でname = decorator(name)を使用して後でデコレータを適用することも、クラスコンテキストでcls.name = decorator(cls.name)を使用していつでも適用することもできます。これを適用するメソッドを選択して選択する必要がある場合は、メソッド名やメソッドに設定された属性、メソッドのdocstringなどの条件を選択するか、メソッドに直接デコレータを使用します。

+0

私はちょうどテストをしました。準備された名前空間(__prepare__)の名前は実際にクラスで使用できます。だから、メタクラスがこれを行うことができます。 –

+0

@SamHartman:私もテストを実行しましたが、今私はエラーを見ました。再テストします。 –

+0

@SamHartman:ええと、私が作ったメタクラスでタイプミス。 –

3

私はメタクラスがこれを行うことができると言うことができます。あなたは何とかおそらくインポートすることで、メタクラスに利用できるデコレータを取得する必要があり、その後、あなたは準備された名前空間にそれらを含めることができます。

class includewraps(type): 
    def prepare(*args): 
     from functools import wraps 
     return {'wraps': wraps} 


class haswraps (metaclass = includewraps): 
    # wraps is available in this scope 
2

は、文字列のリストを取り、関数である前にそれらをインポートデコレータを書きます初めて呼ばれた。これにより、明示的なインポートが必要になる前の最後の瞬間まで避けられます。これは他のすべての回答と同様に、おそらくコードを再構成する必要があることを示すものです。

from functools import wraps 
from importlib import import_module 

def deferred(*names): 
    def decorator(f): 
     # this will hold the fully decorated function 
     final_f = None 

     @wraps(f) 
     def wrapper(*args, **kwargs): 
      nonlocal final_f 

      if final_f is None: 
       # start with the initial function 
       final_f = f 

       for name in names: 
        # assume the last . is the object to import 
        # import the module then get the object 
        mod, obj = name.rsplit('.', 1) 
        d = getattr(import_module(mod), obj) 
        # decorate the function and keep going 
        final_f = d(final_f) 

      return final_f(*args, **kwargs) 

     return wrapper 

    return decorator 
# for demonstration purposes, decorate with a function defined after this 
# assumes this file is called "example.py" 
@deferred('example.double') 
def add(x, y): 
    return x + y 

def double(f): 
    @wraps(f) 
    def wrapper(*args, **kwargs): 
     return 2 * f(*args, **kwargs) 

    return wrapper 

if __name__ == '__main__': 
    print(add(3, 6)) # 18 

deferredへの引数は、フォーム'path.to.module.decoratorの文字列でなければなりません。 path.to.moduleがインポートされ、decoratorがモジュールから取得されます。各デコレータは、関数をラップするために適用されます。ファンクションはnonlocalに格納されているため、このインポートおよびデコレートは、ファンクションが初めて呼び出されたときにのみ行う必要があります。

関連する問題