2017-02-15 6 views
2

これは私の最初の質問です。私はこの問題について多くのことを尋ねましたが、私はそれに対処する有益な情報は見つけられませんでした。functools.wrapsは、mappingproxy型の代わりにdict型の__dict__を返します

Python 3で私のコードの一般的な推奨されないPythonデコレータを作成しようとしています。目的は2つのことを行う関数またはクラスを正しくラップすることです:deprecationに関する警告を表示し、__doc__の前にdeprecation情報sphinxによっても処理することができます)。私は、私が望むものの大部分を整理したと思いますが、私はまだスフィンクスと闘っていますが、functools.wrapsの行動について興味があるものが見つかりました。基本的に、ラップされたクラスの__dict__はタイプdictであり、ラップされていないクラスのタイプはmappingproxyです。ここで

は、いくつかのショーケースコードです:

import functools 

class Deprecated(object): 
    def __call__(self, cls): 
     myassigns = functools.WRAPPER_ASSIGNMENTS 
     myassigns += ('__mro__',) 

     @functools.wraps(cls, assigned=myassigns) 
     def new_func(*args, **kwargs): 
      warnings.warn(message, category=DeprecationWarning, stacklevel=2) 
      return cls(*args, **kwargs) 

     return new_func 


@Deprecated() 
class SomeOldClass: 
    def some_method(self, x, y): 
     return x + y 

class SomeClass: 
    def some_method(self, x, y): 
     return x + y 

print(type(SomeOldClass.__dict__)) 
print(type(SomeClass.__dict__)) 

そして出力:

<class 'dict'> 
<class 'mappingproxy'> 

functools.wraps誤動作ですか私が何か間違ったことをやっていますか?

+3

"ラップされたクラス"は関数です。 'functools.wraps'は何をすると思いましたか? – user2357112

+0

'some_method'をラップすることを意味しましたか?それは、クラスそのものをラップしたものです(深くは見ていませんが、インスタンスを作成するときに警告する必要があると思います)が、名前はクラスではなくラッパー関数を参照するということも意味します'SomeOldClass'では明示的に' __wrapped__'属性にドリルスルーすることなくクラス関連のものは表示されません。このようにクラスを非推奨にするには、ラッパーがクラスにドリルして明示的に '__init__'または' __new__'をラップして(元の 'cls'を返して)、クラスをラップする関数を返しません。 – ShadowRanger

+0

@ user2357112私は私のラッピング関数を隠すことを期待しています。私の変更を除いて、元のクラスであることが表示されます。具体的には、私は廃止されたClassで 'help()'のような関数を適切に動作させたいと思っていました。 – j3mdamas

答えて

0

それはクラス自体をラップしないように、私は強く、あなたのラッパークラスを認識することをお勧めしたい:

class Deprecated(object): 
    def __init__(self, messagefmt='{wrapped} is deprecated'): 
     self.messagefmt = messagefmt 

    def __call__(self, wrapped): 
     message = self.messagefmt.format(wrapped=wrapped.__name__) 
     wrappingclass = isinstance(wrapped, type) 
     if wrappingclass: 
      wrappedfunc = wrapped.__init__ 
     else: 
      wrappedfunc = wrapped 

     @functools.wraps(wrappedfunc) 
     def new_func(*args, **kwargs): 
      warnings.warn(message, category=DeprecationWarning, stacklevel=2) 
      return wrappedfunc(*args, **kwargs) 
     wrapped.__doc__ = 'Deprecated\n\n' + message + '\n\n' + (wrapped.__doc__ or '') 

     if wrappingclass: 
      wrapped.__init__ = new_func 
      return wrapped 
     else: 
      return new_func 

をこれは、あなたが両方のクラスでそれを使用することができます:

@Deprecated() 
class SomeOldClass: 
    def some_method(self, x, y): 
     return x + y 

隠さずクラスの振る舞いと関数(おそらくカスタムの非推奨メッセージがあります):

@Deprecated('foo is dead') 
def foo(): 
    pass 

だから、それらを使用する場合:

>>> import warnings 
>>> warnings.filterwarnings('always', category=DeprecationWarning) 
>>> SomeOldClass() 
/usr/local/bin/ipython3:1: DeprecationWarning: SomeOldClass is deprecated 
    #!/usr/bin/python3 
<__main__.SomeOldClass at 0x7fea8f744a20> 
>>> foo() 
/usr/local/bin/ipython3:1: DeprecationWarning: foo is dead 
    #!/usr/bin/python3 

と同様、両方のための__doc__は非推奨通知を付加するように変更されたが(あなたがそれをSphinxifyする微調整することができ、私は基本的な例を提供していました)。

+0

ありがとう!これは私が探していた答えです!私はそれをテストするつもりですが、私はそれが動作すると確信しています。 – j3mdamas

+0

@ j3mdamas:私は表示された場合にそれをテストしました(そして、 '__doc__'文字列を適切に変更できること、' class'がクラスのままであることなどを確認しました)。たとえば、クラスと非クラスのテストはPy2では古いスタイルのクラスではないので、古いスタイルのクラスをサポートするにはちょっと微調整する必要があります)。それにかかわらず、最小限の微調整が必​​要で、かなり近いはずです。 – ShadowRanger

+0

わずかなコメント:メソッドの場合、 '__doc__'は更新されていませんでした。私は '@ functools.wraps(wrappedfunc)'の前に割り当てを移動し、それが動作し始めました。 – j3mdamas

0

は、不正確ではないし、技術的に何か間違っています。と呼ばれる機能を返しました。がインスタンスを返します。

>>> SomeOldClass 
<function __main__.SomeOldClass> 

ないクラス:ラッピングした後

SomeOldClassはあなたが包まれ、返される関数new_funcです。未修飾のSomeClassは、__dict__mappingproxyのクラスとして保存されています。

+0

あなたの答えをありがとう!しかし、装飾されたクラスの実例を見つけるのは難しいので、私はこのアプローチを行っていました。 – j3mdamas

関連する問題