PythonCookbook第8章(メタプログラミング)

9094 ワード

メタプログラミングの主な目標は、関数とクラスを作成し、既存のコードを修正、生成、パッケージするなど、コードを操作することです.Pythonにおけるこの目的に基づく主な特性には、装飾器、クラス装飾器、およびメタクラスが含まれる.
 
9.1関数にパッケージを追加
に質問
関数にパッケージを追加して追加の処理を追加したいです.
ソリューション
簡単な装飾器を書く
import time
from functools import wraps


def timethis(func):
    '''
    :param func:  Decorator that reports the execution time
    :return: func
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result
    #       
    return wrapper

@timethis
def countdown(n):

    while n > 0:
        n -= 1

if __name__ == '__main__':
    print(countdown(10000))
    print(countdown(10000000))

ディスカッション:
アクセラレータは、入力として関数を受信し、出力として新しい関数を返す関数です.
@timethis
def countdown(n):

これはcountdown=timethis(countdown)という意味です
クラスに組み込まれている@staticmethod,@classmethos,@propertyは同じ論理です
 
9.2デザイナの作成時に関数のメタデータをどのように保存するか.
質問:
デザイナを作成しましたが、関数に使用すると、関数名、ドキュメント文字列、関数コメント、呼び出し署名などの重要なメタデータが失われます.
ソリューション:
functools.wraps
import time
from functools import wraps


def timethis(func):
    '''
    :param func:  Decorator that reports the execution time
    :return: func
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result
    #       
    return wrapper

@timethis
def countdown(n: int) -> int:
    '''this is countdown'''
    while n > 0:
        n -= 1
    return n

if __name__ == '__main__':
    print(countdown(10000))
    print(countdown(10000000))
    #       
    print(countdown.__annotations__)
    #     
    print(countdown.__name__)
    #       
    print(countdown.__doc__)

 
countdown 0.0006299018859863281
0
countdown 0.5314240455627441
0
{'n': , 'return': }
countdown
this is countdown

ディスカッション:
@wrapsをキャンセルした場合
関数の特性がなくなりました
countdown 0.0007231235504150391
0
countdown 0.5646867752075195
0
{}
wrapper
None

 
 #      
    print(countdown.__wrapped__)
    from inspect import signature
    print(signature(countdown))
    print(signature(countdown.__wrapped__))

被装飾関数の_wrapped__飾られていない関数を取り戻す
 
9.3装飾器の解包装
質問:
パッケージされていない関数を取り戻す
ソリューション:
パス_wrapped__属性のフェッチ
ディスカッション:
装飾器具にfunctoolsを利用すればwraps(func)はメタデータを適切にコピーしてから__を使うことができる.wrapped__を選択します.
複数のデコレーションをしているとき、見て_wrapped__に表示されます.
from functools import wraps

def decorator1(func):
    @wraps(func)
    def wrapper(*args):
        print('Decorator1')
        return func(*args)
    return wrapper

def decorator2(func):
    @wraps(func)
    def wrapper(*args):
        print('Decorator2')
        return func(*args)

    return wrapper

@decorator1
@decorator2
def add(x, y):
    return x+y

if __name__ == '__main__':
    print(add(2,3))
    print('=' * 10 )
    #     decorator2  
    print(add.__wrapped__(3,4))
    print('=' * 10)
    #          
    print(add.__wrapped__.__wrapped__(3, 4))

 
/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t3_3.py
Decorator1
Decorator2
5
==========
Decorator2
7
==========
7

Process finished with exit code 0

 Python3.7この脆弱性は修正されており、要素の関数に直接通り抜けることはありません.
ただし、すべての装飾品に@wrapsが使用されているわけではありません.そのため、一部の装飾品の動作は、特に組み込まれた装飾品@staitcmethodと@classmethodによって作成された記述子オブジェクトがこの規則に従っていない可能性があります(逆に、元の関数は__func__属性に保存されます).
 
9.4パラメータを受け取ることができる装飾器を定義する
質問:
受信可能な装飾器を作成する
ソリューション:
装飾器工場を編纂して、本の中でloggingモジュールを使って、装飾器工場を編纂して、ちょうど私もloggingモジュールを再復習します
from functools import wraps
import logging
import time

def logged(level, name=None, message=None):

    def decorate(func):
        logname = name if name else func.__name__
        #     log     
        log = logging.getLogger(logname)
        logmsg = message if message else  func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            #   log  
            # log.setLevel(logging.ERROR)
            # print(log.level)
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate

@logged(logging.DEBUG)
def add(x, y):
    return x+y

@logged(logging.CRITICAL, 'example')
def spam():
    print('Spam!')

if __name__ == '__main__':
    print(add(1, 2))
    print('=' * 10)
    time.sleep(1)
    print(spam())

 
/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t4_2.py
3
==========
spam
Spam!
None

Process finished with exit code 0

装飾器工場を通過する主な役割はパラメータを内部関数呼び出しに伝達することであり,ここではloggingのレベルが伝達される.
ディスカッション
@decorator(x, y, z)
def func(a, b):
  ...
実は最下位で動いているのは
func = decorator(x,y,z)(func)
decorator(x,y,z)は、呼び出し可能なオブジェクトを返さなければなりません.
 
9.5ユーザーによって変更できるアトリビュートを定義するアクセラレータ
に質問
関数をパッケージするために装飾器を作成したいのですが、ユーザーに装飾器のプロパティを調整させ、実行時に装飾器の動作を制御することができます.
ソリューション:
アクセサ関数を作成し、nonlocalキーワード変数でアクセラレータ内部のプロパティを変更します.その後,アクセサ関数を関数属性としてパッケージ関数に付加する.
自分で書いたテストでは、アクセサ関数を必要とせず、パッケージ関数の外でパッケージ関数の属性を関数として定義します.
from functools import wraps, partial
import logging
import time


logging.basicConfig(level=logging.DEBUG)

#      ,              ,   partial  
def attach_wrapper(obj, func=None):
    if func is None:
        return partial(attach_wrapper, obj)
    setattr(obj, func.__name__, func)
    return func

def logged(level, name=None, message=None):

    def decorate(func):
        # print(func.__wrapped__)
        # print(func.__name__)
        logname = name if name else func.__name__
        #     log     
        log = logging.getLogger(logname)
        logmsg = message if message else  func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            #   log  
            # log.setLevel(logging.ERROR)
            # print(log.level)
            log.log(level, logmsg)
            return func(*args, **kwargs)

        #                    ,    wrapper  ,      func  
        #                 
        @attach_wrapper(wrapper)
        def set_level(newlevel):
            nonlocal level
            level = newlevel

        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg

        wrapper.get_level = lambda :level
        wrapper.name = 'sidian'

        return wrapper
    return decorate

@logged(logging.DEBUG)
def add(x, y):
    return x+y

@logged(logging.CRITICAL, 'example')
def spam():
    print('Spam!')

if __name__ == '__main__':
    print(add.set_message('Hello World'))
    print(add(1, 2))
    print('=' * 10)
    time.sleep(1)
    print(spam())

 
/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t5_2.py
DEBUG:add:Hello World
None
3
==========
Spam!
None
CRITICAL:example:spam

実は私自身が書いているうちに、書かないで関数にアクセスすることに気づきました.
wrapper.get_level = lambda :level
wrapper.name = 'sidian'

内部関数を定義し、内部関数に属性を関数として追加することもできます.相対的に手動で追加することはできますが、ライト・アクセサ関数を避けることができます.
ディスカッション:
アクセサリーはすべて@functoosを使用しています.wrapでは,内層関数は約複数の装飾器層を含んで伝播することができる.
 
9.6オプションのパラメータを受け取ることができる装飾器具を定義する
質問:
@decoratorのようにパラメータなしで使用できるように、@decorator(x,y,z)のように装飾工場で使用できるように、個別の装飾器を作成したいと思っています.
ソリューション:
装飾関数を定義するパラメータには*があり、functools.partailは関数を返します.
from functools import wraps
import logging
import functools
import time

def logged(func=None, *, level=logging.WARNING, name=None, message=None):
    #       func,     partial      ,         ,           
    if func is None:
        return functools.partial(logged, level=level, name=name, message=message)


    logname = name if name else func.__name__
    #     log     
    log = logging.getLogger(logname)
    logmsg = message if message else  func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        #   log  
        # log.setLevel(logging.ERROR)
        # print(log.level)
        log.log(level, logmsg)
        return func(*args, **kwargs)
    return wrapper


@logged
def add(x, y):
    return x+y

@logged(level=logging.CRITICAL, name='example')
def spam():
    print('Spam!')

if __name__ == '__main__':
    print(add(1, 2))
    print('=' * 10)
    time.sleep(1)
    print(spam())

ディスカッション:
全体のポイントを理解するには
@decorator(x, y, z)
def func(a, b):
  ...
実は最下位で動いているのは
func=decorator(x,y,z)(func)これは重中の重さです
decorator(x,y,z)は、呼び出し可能なオブジェクトを返さなければなりません.