Pythonアクセサリー(Decorator)完全ガイド-進級編

9966 ワード

Decoratorステップアップガイド
[pythonデザイナ完全ガイド基礎編]では、pythonのデザイナは本質的に1つの関数オブジェクトを入力として受け入れ、その関数の前後にいくつかの新しい論理を追加し、パッケージされた新しい関数オブジェクトの関数を返すことを知っています.
質問1:パラメータを入力した関数のある装飾器
基礎編では,我々が論じたすべての装飾器で装飾された関数は,入力パラメータを受信しない関数である.では、入力パラメータのある関数について、それらに適した装飾器をどのように定義すればいいのでしょうか.次のコードクリップを参照してください.
def decorator_for_func_with_arguments(func_to_decorate):
    def wrapper_that_passes_through_arguments(arg1, arg2):
        print('I got args! Look: {} {}'.format(arg1, arg2))
        func_to_decorate(arg1, arg2)
    return wrapper_that_passes_through_arguments

@ decorator_for_func_with_arguments
def print_full_name(first_name, last_name):
    print('I am {} {}'.format(first_name, last_name))

print_full_name('Tony', 'Stark')
# output: 
# I got args! Look: Tony Stark
# I am Tony Stark

装飾が必要な関数の代わりに装飾器が実際に新しい関数を返すため,装飾器で返される新しい関数が元の関数で受け入れられるパラメータフォーマットと一致することを確保するだけでよいことが分かった.
問題2:類の方法の装飾器
Pythonのクラスメソッドは、実際には関数と同様に、現在のインスタンスの参照を第1のパラメータとして固定的に受信するだけであり、一般的にはselfと表記される.では、クラスメソッドを装飾できる装飾器は、実際には問題1と一致する方法で実現することもできるが、返される関数が受信する最初のパラメータも、現在のインスタンスの参照であることを常に確保すればよい(self).以下に示す.
def decorator_for_instance_method(method_to_decorate):
    def wrapper(self, bonus):
        #     ,       d=====( ̄▽ ̄*)b
        bonus = bonus * 2
        return method_to_decorate(self, bonus)
    return wrapper

class Salary(object):
    def __init__(self):
        self.base = 666

    @decorator_for_instance_method
    def total_compensation(self, bonus):
        print('Congrats! You got a total compensation of {}'.format(self.base * 12 + bonus))

salary_instance = Salary()
salary_instance.total_compensation(2048)
# output: Congrats! You got a total compensation of 12088

同様に、pythonのargsおよび*kwargsを用いて、任意の数のパラメータ関数を受信する装飾器を実装することもできる.以下に示す.
def decorator_passing_arbitrary_arguments(function_to_decorate):
    def wrapper_with_arbitrary_arguments(*args, **kwargs):
        print('Received arguments as following')
        print(args)
        print(kwargs)

        function_to_decorate(*args, **kwargs)

    return wrapper_with_arbitrary_arguments

@decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print('This function does not have any argument')

function_with_no_argument()
# output:
# Received arguments as following
# ()
# {}
# This function does not have any argument

@decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print('This function has arguments')

function_with_arguments(1,2,3)
# output:
# Received arguments as following
# (1, 2, 3)
# {}
# This function has arguments

@decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, name)
    print('{}, {}, {}'.format(a, b, c))
    print('{}'.format(name))

function_with_named_arguments(1, 2, 3, name='robot')
# output:
# Received arguments as following
# (1, 2, 3)
# {'name': 'robot'}
# 1, 2, 3
# robot

class Salary(object):
    def __init__(self):
        self.base = 666

    @decorator_passing_arbitrary_arguments
    def total_compensation(self, bonus):
        print('Congrats! You got a total compensation of {}'.format(self.base * 12 + bonus))

salary = Salary()
salary.total_compensation(2048)
# salary.total_compensation(2048)
# Received arguments as following
# (<__main__.salary object="" at="">, 2048)
# {}
# Congrats! You got a total compensation of 10040

質問3:アクセサリーにパラメータを入力
装飾器が装飾する関数パラメータに関する問題を論じた.次に,パラメータを受信できる装飾器をどのように実現するかについて議論する.
以前の文章を読んだ読者に比べて、装飾器とは入力として1つの関数を受信し、別の関数を返す関数であることが分かった.この場合,装飾器の関数署名は固定されているため,入力関数以外のパラメータを直接入力することはできない.次のコードに示すように.
#                   
def my_decorator(func):
    print('This is an ordinary function')
    def wrapper():
        print('This is the wrapper function that will be returned')
        func()

#                   
#       
# lazy_function = my_decorator(lazy_function)
@my_decorator
def lazy_function():
    print('zzzzz')

# output:
# This is an ordinary function

lazy_function()
# output:
# This is the wrapper function that will be returned
# zzzzz

上記のコードは、@my_decoratorを使用すると、実際にはlazy_function = my_decorator(lazy_function)が実行されることを示しています.そのため、装飾器の署名を直接変更することができない場合、装飾器に戻ってパラメータを受信し、閉包(閉包の概念に慣れていない読者は、この文章を参照)を使用して装飾器に渡すことができる関数を実現するために、いくつかの他の方法を採用する必要があります.言い換えれば、装飾器を動的に生成するために装飾器工場関数が必要です.
まず,次のコードに示すように,装飾器工場関数を実現した.
def decorator_maker():
    print('This is a factory generating decorators on the fly. This function is called once we want a fresh decorator.')

    def my_decorator(func):
        print('This is the decorator generated on the fly. This function is called when the decoration happens.')
        #    ,             wrapper      
        def wrapper():
            print('This is the wrapper around the decorated function. This function is called once the decorated function is called.')
            return func()

        return wrapper

    print('The decorator function created on the fly is returned.')
    return my_decorator

def func():
    print('This is the function decorated.')

fresh_decorator = decorator_maker()
# output:
# This is a factory generating decorators on the fly. This function is called once we want a fresh decorator.
# The decorator function created on the fly is returned.

func = fresh_decorator(func)
# output:
# This is the decorator generated on the fly. This function is called when the decoration happens.

func()
# output:
# This is the wrapper around the decorated function. This function is called once the decorated function is called.
# This is the function decorated.

上記のコードに基づいて、この装飾工場関数をよりpythonic的に使用することができます.以下に示す.
@decorator_maker()
def func():
    print('This is the function decorated.')
# output:
# This is a factory generating decorators on the fly. This function is called once we want a fresh decorator.
# The decorator function created on the fly is returned.
# This is the decorator generated on the fly. This function is called when the decoration happens.

func()
# output:
# This is the wrapper around the decorated function. This function is called once the decorated function is called.
# This is the function decorated.

上記の例では、@構文の装飾器として実際に1つの装飾工場で返される関数として使用できることを示しています.それなら,この工場関数にパラメータを伝達することも自然に可能である.次のコードに示すように.
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
    print('This is the decorator factory. Input arguments are: {}, {}.'.format(decorator_arg1, decorator_arg2))

    def my_decorator(func):
        #                             
        print('This is the decorator function created on the fly.')
        print('Arguments are passed in from outer function using closure: {}, {}.'.format(decorator_arg1, decorator_arg2))

        #     wrapper                  
        #                 
        def wrapper(function_arg1, function_arg2):
            print('This is the wrapper around the decorated function.')
            print('This function can access all the variables.')
            print('Variables from the decorator factory: {}, {}.'.format(decorator_arg1, decorator_arg2))
            print('Variables from the decorated function: {}, {}.'.format(function_arg1, function_arg2))
            return func(function_arg1, function_arg2)

        return wrapper
    return my_decorator

@decorator_maker_with_arguments('Avengers', 'Justice League')
def func(function_arg1, function_arg2):
    print('This is the function decorated.')
    print('It accepts two arguments: {}, {}.'.format(function_arg1, function_arg2))

# output:
# This is the decorator factory. Input arguments are: Avengers, Justice League.
# This is the decorator function created on the fly.
# Arguments are passed in from outer function using closure: Avengers, Justice League.

func('Captain America', 'Bat Man')
# output:
# This is the wrapper around the decorated function.
# This function can access all the variables.
# Variables from the decorator factory: Avengers, Justice League.
# Variables from the decorated function: Captain America, Bat Man.
# This is the function decorated.
# It accepts two arguments: Captain America, Bat Man.

上記の例では,文字列定数をパラメータとして装飾工場関数に渡した.通常のpython関数と同様に、この関数は変数をパラメータとして受信することもできます.下記の通りです.
a1 = 'Avenagers'
a2 = Justice League'
b1 = 'Captain America'
b2 = 'Bat Man'

@decorator_maker_with_arguments(a1, a2)
def func(function_arg1, function_arg2):
    print('This is the function decorated.')
    print('It accepts two arguments: {}, {}.'.format(function_arg1, function_arg2))

# output:
# This is the decorator factory. Input arguments are: Avengers, Justice League.
# This is the decorator function created on the fly.
# Arguments are passed in from outer function using closure: Avengers, Justice League.

func(b1, b2)
# output:
# This is the wrapper around the decorated function.
# This function can access all the variables.
# Variables from the decorator factory: Avengers, Justice League.
# Variables from the decorated function: Captain America, Bat Man.
# This is the function decorated.
# It accepts two arguments: Captain America, Bat Man.

以上の議論から,装飾工場関数では呼び出しが通常関数と全く同じであることが分かる.*argsおよび**kwargsを用いて可変長のパラメータを受け入れることもできる.しかし、装飾器装飾関数のプロセスは一度だけ発生します.それはpythonコンパイラimportの現在のコードファイルの場合です.したがって、ファイルがimportされると、デザイナファクトリが受信したパラメータを変更することはできません.生成したデザイナが装飾された関数を変更することはできません.
Reference文の一部は以下の文章から翻訳されています.翻訳の一部の著作権は原作者の所有に帰属する.https://gist.github.com/Zearin/2f40b7b9cfc51132851a