简体   繁体   English

可选的 args,装饰器的 kwargs

[英]Optional args, kwargs to decorator

I am using the following approach to pass in an optional argument to a decorator:我正在使用以下方法将可选参数传递给装饰器:

def wait(func=None, delay=1.0):
    def decorator_wait(func):
        def wrapper_wait(*args, **kwargs):
            time.sleep(delay)
            return func(*args, **kwargs)
        return wrapper_wait
    return decorator_wait(func) if func is not None else decorator_wait


@wait
def print_something(something):
    print (something)

@wait(delay=0.2)
def print_something_else(something):
    print (something)

The above code looks pretty difficult to follow though with all the nesting.尽管所有的嵌套,上面的代码看起来都很难理解。 Is there another approach to do the above, or is this the only method available for something like this?是否有另一种方法来执行上述操作,或者这是唯一可用于此类事情的方法?

You can write classes having a __call__ method, instead of writing a bunch of nested def s.您可以编写具有__call__方法的类,而不是编写一堆嵌套的def

It sounds like you want a decorator Wait which haults program execution for a few seconds.听起来你想要一个装饰器Wait来拖拽程序执行几秒钟。 If you don't pass in a Wait-time then the default value is 1 seconds.如果您不传递等待时间,则默认值为 1 秒。 Use-cases are shown below.用例如下所示。

##################################################
@Wait
def print_something(something):
    print(something)

##################################################
@Wait(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Wait(delay=3)
def print_something_else(something_else):
    print(something_else)

When Wait has an argument, such as @Wait(3) , then the call Wait(3) is executed before anything else happens.Wait有一个参数时,例如@Wait(3) ,则调用Wait(3)会在其他任何事情发生之前执行。

That is, the following two pieces of code are equivalent也就是下面两段代码是等价的

@Wait(3)
def print_something_else(something_else):
    print(something_else)
    
###############################################
return_value = Wait(3)
@return_value
def print_something_else(something_else):
    print(something_else)
    

This is a problem.这是个问题。

if `Wait` has no arguments:
    `Wait` is the decorator.
else: # `Wait` receives arguments
    `Wait` is not the decorator itself.
    Instead, `Wait` ***returns*** the decorator
    

One solution is shown below:一种解决方案如下所示:

Let us begin by creating the following class, DelayedDecorator :让我们从创建以下 class, DelayedDecorator

import io
class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Delayed Decorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Delayed Decorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "If only one input, input must be callable",
                    "Instead, received:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)
    

Now we can write things like:现在我们可以编写如下内容:

 dec = DelayedDecorator(Wait, delay=4)
 @dec
 def delayed_print(something):
    print(something)

Note that:注意:

  • dec does not not accept multiple arguments. dec不接受多个 arguments。
  • dec only accepts the function to be wrapped. dec仅接受要包装的 function。
import inspect
class PolyArgDecoratorMeta(type):
    def __call__(Wait, *args, **kwargs):
        try:
            arg_count = len(args)
            if (arg_count == 1):
                if callable(args[0]):
                    SuperClass = inspect.getmro(PolyArgDecoratorMeta)[1]
                    r = SuperClass.__call__(Wait, args[0])
                else:
                    r = DelayedDecorator(Wait, *args, **kwargs)
            else:
                r = DelayedDecorator(Wait, *args, **kwargs)
        finally:
            pass
        return r
    
import time
class Wait(metaclass=PolyArgDecoratorMeta):
    def __init__(i, func, delay = 2):
        i._func = func
        i._delay = delay
        
    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 

The following two pieces of code are equivalent:以下两段代码是等价的:

@Wait
def print_something(something):
     print (something)

##################################################

def print_something(something):
    print(something)
print_something = Wait(print_something)

We can print "something" to the console very slowly, as follows:我们可以非常缓慢地将"something"打印到控制台,如下:

print_something("something")

#################################################
@Wait(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Wait, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("something") 
     

Final Notes最后的笔记

It may look like a lot of code, but you don't have to write the classes DelayedDecorator and PolyArgDecoratorMeta every-time.它可能看起来有很多代码,但您不必每次都编写DelayedDecoratorPolyArgDecoratorMeta类。 The only code you have to personally write something like as follows, which is fairly short:您必须亲自编写的唯一代码如下,相当短:

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Wait(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r

You can avoid having to remember "do I need to call this or not?"您可以避免记住“我需要打电话给这个吗?” by removing the func argument from the wait function, and remembering to always call your decorator-returner.通过从wait function 中删除func参数,并记住始终调用您的装饰器返回器。

It would look like this:它看起来像这样:

def wait(delay=1.0):
    def decorator_wait(func):
        def wrapper_wait(*args, **kwargs):
            time.sleep(delay)
            return func(*args, **kwargs)
        return wrapper_wait
    return decorator_wait


@wait()
def print_something(something):
    print (something)

@wait(delay=0.2)
def print_something_else(something):
    print (something)

print_something("hello")
# 1 second delay, then 'hello'
print_something_else("there")
# 0.2 second delay, then 'there'

You just have to remember that wait will always return the decorator, so you have to use () when decorating your functions.你只需要记住wait总是返回装饰器,所以你必须在装饰你的函数时使用()

I think it is a little bit better:我认为它好一点:

import functools
import time

def wait(func=None, delay=1.0):
    if func is None:
        return lambda func: wait(func=func, delay=delay)

    @functools.wraps(func) # this is good practice to use it see: https://stackoverflow.com/questions/308999/what-does-functools-wraps-do
    def _wrapper(*args, **kwargs):
        time.sleep(delay)
        return func(*args, **kwargs)
    return _wrapper


@wait
def test():
    return

@wait(delay=3)
def test2():
    return

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM