_from functools import wraps
def foo_register(method_name=None):
"""Does stuff."""
def decorator(method):
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
return decorator
_
例:以下は、decorator
にする代わりに_my_function
_を_foo_register
_で修飾します。
_@foo_register
def my_function():
print('hi...')
_
例:以下は期待どおりに機能します。
_@foo_register('say_hi')
def my_function():
print('hi...')
_
両方のアプリケーションで正しく機能させる場合(1つは_method.__name__
_を使用し、もう1つは名前を渡す)、_foo_register
_の内部をチェックして、最初の引数がデコレータかどうかを確認する必要があります。 、私はしなければなりません:return decorator(method_name)
(_return decorator
_の代わりに)。この種の「呼び出し可能かどうかを確認する」は非常にハックのようです。このような多目的デコレータを作成するより良い方法はありますか?
追伸デコレータの呼び出しを要求できることはすでに知っていますが、これは「解決策」ではありません。 APIを自然に感じてほしい。私の妻は装飾が大好きで、私はそれを台無しにしたくありません。
グレン-私はそれをしなければなりませんでした。それを行う「魔法の」方法がないのは嬉しいことだと思います。私はそれらが嫌いです。
だから、ここに私自身の答えがあります(メソッド名は上記とは異なりますが、概念は同じです)。
from functools import wraps
def register_gw_method(method_or_name):
"""Cool!"""
def decorator(method):
if callable(method_or_name):
method.gw_method = method.__name__
else:
method.gw_method = method_or_name
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
if callable(method_or_name):
return decorator(method_or_name)
return decorator
使用例(どちらのバージョンも同じように機能します):
@register_gw_method
def my_function():
print('hi...')
@register_gw_method('say_hi')
def my_function():
print('hi...')
これを行うために私が知っている最もクリーンな方法は次のとおりです。
import functools
def decorator(original_function=None, optional_argument1=None, optional_argument2=None, ...):
def _decorate(function):
@functools.wraps(function)
def wrapped_function(*args, **kwargs):
...
return wrapped_function
if original_function:
return _decorate(original_function)
return _decorate
説明
次のようなオプションの引数なしでデコレータが呼び出された場合:
@decorator
def function ...
関数は最初の引数として渡され、decorateは期待どおりに装飾された関数を返します。
デコレータが次のような1つ以上のオプションの引数で呼び出された場合:
@decorator(optional_argument1='some value')
def function ....
次に、値Noneの関数引数を使用してデコレーターが呼び出されるため、期待どおりに修飾する関数が返されます。
Python
上記のデコレータの署名はPython 3-specific *,
構文を使用して、キーワード引数を安全に使用できます。最も外側の関数のシグネチャを次のものに置き換えるだけです。
def decorator(original_function=None, *, optional_argument1=None, optional_argument2=None, ...):
ここやその他の回答と試行錯誤の助けを借りて、デコレータがオプションの引数を取るようにする実際にはるかに簡単で一般的な方法があることがわかりました。呼び出された引数をチェックしますが、それを行う他の方法はありません。
重要なのはデコレーターを装飾するです。
これがデコレータデコレータです(このコードは汎用であり、オプションの引数デコレータを必要とするすべての人が使用できます):
def optional_arg_decorator(fn):
def wrapped_decorator(*args):
if len(args) == 1 and callable(args[0]):
return fn(args[0])
else:
def real_decorator(decoratee):
return fn(decoratee, *args)
return real_decorator
return wrapped_decorator
使い方は次のように簡単です:
optional_arg_decorator
でデコレータを飾ります例:
@optional_arg_decorator
def example_decorator_with_args(fn, optional_arg = 'Default Value'):
...
return fn
したがって、あなたのケースでは、渡されたメソッド名または__name__
ifNoneを使用して関数の属性を保存するには:
@optional_arg_decorator
def register_method(fn, method_name = None):
fn.gw_method = method_name or fn.__name__
return fn
これで、使用可能なデコレータができましたargsの有無にかかわらず:
@register_method('Custom Name')
def custom_name():
pass
@register_method
def default_name():
pass
assert custom_name.gw_method == 'Custom Name'
assert default_name.gw_method == 'default_name'
print 'Test passes :)'
いかがですか
from functools import wraps, partial
def foo_register(method=None, string=None):
if not callable(method):
return partial(foo_register, string=method)
method.gw_method = string or method.__name__
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
これが @ Nicoleの答え を次のように拡張したものです。
import functools
def optional_arg_decorator(fn):
@functools.wraps(fn)
def wrapped_decorator(*args, **kwargs):
is_bound_method = hasattr(args[0], fn.__name__) if args else False
if is_bound_method:
klass = args[0]
args = args[1:]
# If no arguments were passed...
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
if is_bound_method:
return fn(klass, args[0])
else:
return fn(args[0])
else:
def real_decorator(decoratee):
if is_bound_method:
return fn(klass, decoratee, *args, **kwargs)
else:
return fn(decoratee, *args, **kwargs)
return real_decorator
return wrapped_decorator
とにかく、この古いスレッドがトップに戻ったので、lemmeはいくつかのDecorator-ceptionをスローします。
_def magical_decorator(decorator):
@wraps(decorator)
def inner(*args, **kw):
if len(args) == 1 and not kw and callable(args[0]):
return decorator()(args[0])
else:
return decorator(*args, **kw)
return inner
_
これで、魔法のデコレータが1行で完了します。
_@magical_decorator
def foo_register(...):
# bla bla
_
ちなみに、これはどのデコレータでも機能します。 _@foo
_が@foo()
のように(できるだけ近くに)動作するようにします。
デコレーター定義をデコレートするための汎用デコレーター。デコレートされたデコレーターがデフォルトの引数を受け入れることを表現します。デフォルトの引数は、明示的に何も指定されていない場合に設定されます。
from functools import wraps
def default_arguments(*default_args, **default_kwargs):
def _dwrapper(decorator):
@wraps(decorator)
def _fwrapper(*args, **kwargs):
if callable(args[0]) and len(args) == 1 and not kwargs:
return decorator(*default_args, **default_kwargs)(args[0])
return decorator(*args, **kwargs)
return _fwrapper
return _dwrapper
どちらの方法でも使用できます。
from functools import lru_cache # memoization decorator from Python 3
# apply decorator to decorator post definition
lru_cache = (default_arguments(maxsize=100)) (lru_cache)
# could also be:
# @default_arguments(maxsize=100)
# class lru_cache(object):
# def __init__(self, maxsize):
# ...
# def __call__(self, wrapped_function):
# ...
@lru_cache # this works
def fibonacci(n):
...
@lru_cache(200) # this also works
def fibonacci(n):
...
私はこの問題に非常に悩まされ、最終的にそれを解決するためのライブラリー decopatch を書きました。
2つの開発スタイルをサポートしています:nested(python decoratorfactories)とflat(ネストのレベルが1つ少ない)これは、フラットモードでの例の実装方法です。
from decopatch import function_decorator, DECORATED
from makefun import wraps
@function_decorator
def foo_register(method_name=None, method=DECORATED):
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
# create a signature-preserving wrapper
@wraps(method)
def wrapper(*args, **kwargs):
method(*args, **kwargs)
return wrapper
ここではfunctools.wraps
の代わりに makefun.wraps を使用しているため、署名が完全に保持されます(引数が無効な場合、ラッパーはまったく呼び出されません)。
decopatch
は、double-flatと呼ばれる追加の開発スタイルをサポートします。これは、このようなシグニチャー保持関数ラッパーの作成専用です。あなたの例は次のように実装されます:
from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS
@function_decorator
def foo_register(method_name=None,
method=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
# this is directly the wrapper
if method_name is None:
method.gw_method = method.__name__
else:
method.gw_method = method_name
method(*f_args, **f_kwargs)
このスタイルでは、すべてのコードがmethod
の呼び出しで実行されることに注意してください。これは望ましくないかもしれません-装飾時にのみ一度だけ実行したいかもしれません-このため、以前のスタイルの方が良いでしょう。
両方のスタイルが機能することを確認できます。
@foo_register
def my_function():
print('hi...')
@foo_register('say_hi')
def my_function():
print('hi...')
詳細は documentation を確認してください。
複数のデコレーターでこの機能が必要な場合は、デコレーターのコードボイラープレートを回避できます。
from functools import wraps
import inspect
def decorator_defaults(**defined_defaults):
def decorator(f):
args_names = inspect.getargspec(f)[0]
def wrapper(*new_args, **new_kwargs):
defaults = dict(defined_defaults, **new_kwargs)
if len(new_args) == 0:
return f(**defaults)
Elif len(new_args) == 1 and callable(new_args[0]):
return f(**defaults)(new_args[0])
else:
too_many_args = False
if len(new_args) > len(args_names):
too_many_args = True
else:
for i in range(len(new_args)):
arg = new_args[i]
arg_name = args_names[i]
defaults[arg_name] = arg
if len(defaults) > len(args_names):
too_many_args = True
if not too_many_args:
final_defaults = []
for name in args_names:
final_defaults.append(defaults[name])
return f(*final_defaults)
if too_many_args:
raise TypeError("{0}() takes {1} argument(s) "
"but {2} were given".
format(f.__name__,
len(args_names),
len(defaults)))
return wrapper
return decorator
@decorator_defaults(start_val="-=[", end_val="]=-")
def my_text_decorator(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@decorator_defaults(end_val="]=-")
def my_text_decorator2(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@my_text_decorator
def func1a(value):
return value
@my_text_decorator()
def func2a(value):
return value
@my_text_decorator2("-=[")
def func2b(value):
return value
@my_text_decorator(end_val=" ...")
def func3a(value):
return value
@my_text_decorator2("-=[", end_val=" ...")
def func3b(value):
return value
@my_text_decorator("|> ", " <|")
def func4a(value):
return value
@my_text_decorator2("|> ", " <|")
def func4b(value):
return value
@my_text_decorator(end_val=" ...", start_val="|> ")
def func5a(value):
return value
@my_text_decorator2("|> ", end_val=" ...")
def func5b(value):
return value
print(func1a('My sample text')) # func1a -=[My sample text]=-
print(func2a('My sample text')) # func2a -=[My sample text]=-
print(func2b('My sample text')) # func2b -=[My sample text]=-
print(func3a('My sample text')) # func3a -=[My sample text ...
print(func3b('My sample text')) # func3b -=[My sample text ...
print(func4a('My sample text')) # func4a |> My sample text <|
print(func4b('My sample text')) # func4b |> My sample text <|
print(func5a('My sample text')) # func5a |> My sample text ...
print(func5b('My sample text')) # func5b |> My sample text ...
注:1つの引数を関数としてデコレータに渡すことができないという欠点があります。
注2:このデコレーターを改善する方法に関するヒント/メモがある場合は、コードレビューでコメントできます: https://codereview.stackexchange.com/questions/78829/python-decorator-for-optional-arguments-デコレータ
オプションの引数が呼び出し可能である場合にも機能する他のソリューションを次に示します。
def test_equal(func=None, optional_value=None):
if func is not None and optional_value is not None:
# prevent user to set func parameter manually
raise ValueError("Don't set 'func' parameter manually")
if optional_value is None:
optional_value = 10 # The default value (if needed)
def inner(function):
def func_wrapper(*args, **kwargs):
# do something
return function(*args, **kwargs) == optional_value
return func_wrapper
if not func:
return inner
return inner(func)
このようにして、両方の構文が機能します。
@test_equal
def does_return_10():
return 10
@test_equal(optional_value=20)
def does_return_20():
return 20
# does_return_10() return True
# does_return_20() return True
これはかなり簡潔でfunctoolsを使用しない別のバリエーションです。
_def decorator(*args, **kwargs):
def inner_decorator(fn, foo=23, bar=42, abc=None):
'''Always passed <fn>, the function to decorate.
# Do whatever decorating is required.
...
if len(args)==1 and len(kwargs)==0 and callable(args[0]):
return inner_decorator(args[0])
else:
return lambda fn: inner_decorator(fn, *args, **kwargs)
_
_inner_decorator
_を1つのパラメーターのみで呼び出すことができるかどうかに応じて、_@decorator
_、@decorator()
、@decorator(24)
などを実行できます。
これは「デコレータデコレータ」に一般化できます。
_def make_inner_decorator(inner_decorator):
def decorator(*args, **kwargs):
if len(args)==1 and len(kwargs)==0 and callable(args[0]):
return inner_decorator(args[0])
else:
return lambda fn: inner_decorator(fn, *args, **kwargs)
return decorator
@make_inner_decorator
def my_decorator(fn, a=34, b='foo'):
...
@my_decorator
def foo(): ...
@my_decorator()
def foo(): ...
@my_decorator(42)
def foo(): ...
_
呼び出し可能なクラスを使用して引数のタイプと長さをチェックするような同様のソリューション
class decor(object):
def __init__(self, *args, **kwargs):
self.decor_args = args
self.decor_kwargs = kwargs
def __call__(self, *call_args, **call_kwargs):
if callable(self.decor_args[0]) and len(self.decor_args) == 1:
func = self.decor_args[0]
return self.__non_param__call__(func, call_args, call_kwargs)
else:
func = call_args[0]
return self.__param__call__(func)
def __non_param__call__(self, func, call_args, call_kwargs):
print "No args"
return func(*call_args, **call_kwargs)
def __param__call__(self, func):
def wrapper(*args, **kwargs):
print "With Args"
return func(*args, **kwargs)
return wrapper
@decor(a)
def test1(a):
print 'test' + a
@decor
def test2(b):
print 'test' + b
問題を解決するための簡単なパッケージを作成しました
マスターブランチ_pip install git+https://github.com/ferrine/biwrap
_最新リリース_pip install biwrap
_
一部のラッパーにはオプションの引数があり、@wrapper()
呼び出しを避け、代わりに_@wrapper
_を使用することがよくあります。
これは単純なラッパーで機能します
_import biwrap
@biwrap.biwrap
def hiwrap(fn, hi=True):
def new(*args, **kwargs):
if hi:
print('hi')
else:
print('bye')
return fn(*args, **kwargs)
return new
_
定義されたラッパーは両方の方法で使用できます
_@hiwrap
def fn(n):
print(n)
fn(1)
#> hi
#> 1
@hiwrap(hi=False)
def fn(n):
print(n)
fn(1)
#> bye
#> 1
_
biwrap
はバインドされたメソッドでも機能します
_class O:
@hiwrap(hi=False)
def fn(self, n):
print(n)
O().fn(1)
#> bye
#> 1
_
クラスのメソッド/プロパティもサポートされています
_class O:
def __init__(self, n):
self.n = n
@classmethod
@hiwrap
def fn(cls, n):
print(n)
@property
@hiwrap(hi=False)
def num(self):
return self.n
o = O(2)
o.fn(1)
#> hi
#> 1
print(o.num)
#> bye
#> 2
_
通話などの機能もOK
_def fn(n):
print(n)
fn = hiwrap(fn, hi=False)
fn(1)
#> bye
#> 1
_
これがpython3用に書かれた私の解決策です。関数ではなく呼び出し可能なクラスを定義するため、他のアプローチとは異なります。
class flexible_decorator:
def __init__(self, arg="This is default"):
self.arg = arg
def __call__(self, func):
def wrapper(*args, **kwargs):
print("Calling decorated function. arg '%s'" % self.arg)
func(*args, **kwargs)
return wrapper
それでも明示的にデコレータを呼び出す必要があります
@flexible_decorator()
def f(foo):
print(foo)
@flexible_decorator(arg="This is not default")
def g(bar):
print(bar)