标签归档:decorator

如何获取方法参数名称?

问题:如何获取方法参数名称?

鉴于Python函数:

def a_method(arg1, arg2):
    pass

如何提取参数的数量和名称。即,鉴于我有提及func,因此我希望func.[something]返回("arg1", "arg2")

这样做的使用场景是我有一个装饰器,并且我希望以与实际函数作为键一样的顺序使用方法参数。即,"a,b"我调用时装饰工的外观如何a_method("a", "b")

Given the Python function:

def a_method(arg1, arg2):
    pass

How can I extract the number and names of the arguments. I.e., given that I have a reference to func, I want the func.[something] to return ("arg1", "arg2").

The usage scenario for this is that I have a decorator, and I wish to use the method arguments in the same order that they appear for the actual function as a key. I.e., how would the decorator look that printed "a,b" when I call a_method("a", "b")?


回答 0

看一下inspect模块-这将为您检查各种代码对象属性。

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

其他结果是* args和** kwargs变量的名称,以及提供的默认值。即。

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

请注意,在某些Python实现中,某些可调用对象可能不是自省的。例如,在CPython中,C中定义的某些内置函数不提供有关其参数的元数据。结果,ValueError如果您inspect.getfullargspec()在内置函数上使用,将得到一个。

从Python 3.3开始,您可以inspect.signature()用来查看可调用对象的调用签名:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

Take a look at the inspect module – this will do the inspection of the various code object properties for you.

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

The other results are the name of the *args and **kwargs variables, and the defaults provided. ie.

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

Note that some callables may not be introspectable in certain implementations of Python. For Example, in CPython, some built-in functions defined in C provide no metadata about their arguments. As a result, you will get a ValueError if you use inspect.getfullargspec() on a built-in function.

Since Python 3.3, you can use inspect.signature() to see the call signature of a callable object:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

回答 1

在CPython中,参数数量为

a_method.func_code.co_argcount

他们的名字在

a_method.func_code.co_varnames

这些是CPython的实现细节,因此在其他Python实现(例如IronPython和Jython)中可能不起作用。

接受“传递”参数的一种可移植方式是使用签名定义函数func(*args, **kwargs)。这在matplotlib中经常使用,其中外部API层将许多关键字参数传递给较低级别​​的API。

In CPython, the number of arguments is

a_method.func_code.co_argcount

and their names are in the beginning of

a_method.func_code.co_varnames

These are implementation details of CPython, so this probably does not work in other implementations of Python, such as IronPython and Jython.

One portable way to admit “pass-through” arguments is to define your function with the signature func(*args, **kwargs). This is used a lot in e.g. matplotlib, where the outer API layer passes lots of keyword arguments to the lower-level API.


回答 2

在装饰器方法中,可以通过以下方式列出原始方法的参数:

import inspect, itertools 

def my_decorator():

        def decorator(f):

            def wrapper(*args, **kwargs):

                # if you want arguments names as a list:
                args_name = inspect.getargspec(f)[0]
                print(args_name)

                # if you want names and values as a dictionary:
                args_dict = dict(itertools.izip(args_name, args))
                print(args_dict)

                # if you want values as a list:
                args_values = args_dict.values()
                print(args_values)

如果**kwargs对您来说很重要,那么它将有些复杂:

        def wrapper(*args, **kwargs):

            args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
            args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
            args_values = args_dict.values()

例:

@my_decorator()
def my_function(x, y, z=3):
    pass


my_function(1, y=2, z=3, w=0)
# prints:
# ['x', 'y', 'z', 'w']
# {'y': 2, 'x': 1, 'z': 3, 'w': 0}
# [1, 2, 3, 0]

In a decorator method, you can list arguments of the original method in this way:

import inspect, itertools 

def my_decorator():

        def decorator(f):

            def wrapper(*args, **kwargs):

                # if you want arguments names as a list:
                args_name = inspect.getargspec(f)[0]
                print(args_name)

                # if you want names and values as a dictionary:
                args_dict = dict(itertools.izip(args_name, args))
                print(args_dict)

                # if you want values as a list:
                args_values = args_dict.values()
                print(args_values)

If the **kwargs are important for you, then it will be a bit complicated:

        def wrapper(*args, **kwargs):

            args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
            args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
            args_values = args_dict.values()

Example:

@my_decorator()
def my_function(x, y, z=3):
    pass


my_function(1, y=2, z=3, w=0)
# prints:
# ['x', 'y', 'z', 'w']
# {'y': 2, 'x': 1, 'z': 3, 'w': 0}
# [1, 2, 3, 0]

回答 3

我认为您要寻找的是locals方法-


In [6]: def test(a, b):print locals()
   ...: 

In [7]: test(1,2)              
{'a': 1, 'b': 2}

I think what you’re looking for is the locals method –


In [6]: def test(a, b):print locals()
   ...: 

In [7]: test(1,2)              
{'a': 1, 'b': 2}

回答 4

Python 3版本是:

def _get_args_dict(fn, args, kwargs):
    args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
    return {**dict(zip(args_names, args)), **kwargs}

该方法返回一个包含args和kwargs的字典。

The Python 3 version is:

def _get_args_dict(fn, args, kwargs):
    args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
    return {**dict(zip(args_names, args)), **kwargs}

The method returns a dictionary containing both args and kwargs.


回答 5

我认为这可以使用装饰器满足您的需求。

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

运行它,将产生以下输出:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.

Here is something I think will work for what you want, using a decorator.

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

Run it, it will yield the following output:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.

回答 6

Python 3.5以上版本:

DeprecationWarning:不建议使用inspect.getargspec(),而应使用inspect.signature()代替

所以以前:

func_args = inspect.getargspec(function).args

现在:

func_args = list(inspect.signature(function).parameters.keys())

去测试:

'arg' in list(inspect.signature(function).parameters.keys())

假设我们有函数’function’接受参数’arg’,则其求值为True,否则为False。

来自Python控制台的示例:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
>>> import inspect
>>> 'iterable' in list(inspect.signature(sum).parameters.keys())
True

Python 3.5+:

DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() instead

So previously:

func_args = inspect.getargspec(function).args

Now:

func_args = list(inspect.signature(function).parameters.keys())

To test:

'arg' in list(inspect.signature(function).parameters.keys())

Given that we have function ‘function’ which takes argument ‘arg’, this will evaluate as True, otherwise as False.

Example from the Python console:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
>>> import inspect
>>> 'iterable' in list(inspect.signature(sum).parameters.keys())
True

回答 7

在带有Signature对象的Python 3. +中,一种获取参数名称与值之间映射的简单方法是使用Signature的bind()方法!

例如,以下是一个装饰器,用于打印类似的地图:

import inspect

def decorator(f):
    def wrapper(*args, **kwargs):
        bound_args = inspect.signature(f).bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(dict(bound_args.arguments))

        return f(*args, **kwargs)

    return wrapper

@decorator
def foo(x, y, param_with_default="bars", **kwargs):
    pass

foo(1, 2, extra="baz")
# This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}

In Python 3.+ with the Signature object at hand, an easy way to get a mapping between argument names to values, is using the Signature’s bind() method!

For example, here is a decorator for printing a map like that:

import inspect

def decorator(f):
    def wrapper(*args, **kwargs):
        bound_args = inspect.signature(f).bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(dict(bound_args.arguments))

        return f(*args, **kwargs)

    return wrapper

@decorator
def foo(x, y, param_with_default="bars", **kwargs):
    pass

foo(1, 2, extra="baz")
# This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}

回答 8

这是无需使用任何模块即可获取功能参数的另一种方法。

def get_parameters(func):
    keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
    sorter = {j: i for i, j in enumerate(keys[::-1])} 
    values = func.__defaults__[::-1]
    kwargs = {i: j for i, j in zip(keys, values)}
    sorted_args = tuple(
        sorted([i for i in keys if i not in kwargs], key=sorter.get)
    )
    sorted_kwargs = {}
    for i in sorted(kwargs.keys(), key=sorter.get):
        sorted_kwargs[i] = kwargs[i]      
    return sorted_args, sorted_kwargs


def f(a, b, c="hello", d="world"): var = a


print(get_parameters(f))

输出:

(('a', 'b'), {'c': 'hello', 'd': 'world'})

Here is another way to get the function parameters without using any module.

def get_parameters(func):
    keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
    sorter = {j: i for i, j in enumerate(keys[::-1])} 
    values = func.__defaults__[::-1]
    kwargs = {i: j for i, j in zip(keys, values)}
    sorted_args = tuple(
        sorted([i for i in keys if i not in kwargs], key=sorter.get)
    )
    sorted_kwargs = {
        i: kwargs[i] for i in sorted(kwargs.keys(), key=sorter.get)
    }   
    return sorted_args, sorted_kwargs


def f(a, b, c="hello", d="world"): var = a
    

print(get_parameters(f))

Output:

(('a', 'b'), {'c': 'hello', 'd': 'world'})

回答 9

返回参数名称列表,负责部分函数和常规函数:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)

Returns a list of argument names, takes care of partials and regular functions:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)

回答 10

更新Brian的答案

如果Python 3中的函数具有仅关键字参数,则需要使用inspect.getfullargspec

def yay(a, b=10, *, c=20, d=30):
    pass
inspect.getfullargspec(yay)

产生这个:

FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})

Update for Brian’s answer:

If a function in Python 3 has keyword-only arguments, then you need to use inspect.getfullargspec:

def yay(a, b=10, *, c=20, d=30):
    pass
inspect.getfullargspec(yay)

yields this:

FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})

回答 11

在python 3中,下面是make *args**kwargsinto dictOrderedDict用于python <3.6维护dict命令):

from functools import wraps

def display_param(func):
    @wraps(func)
    def wrapper(*args, **kwargs):

        param = inspect.signature(func).parameters
        all_param = {
            k: args[n] if n < len(args) else v.default
            for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
        }
        all_param .update(kwargs)
        print(all_param)

        return func(**all_param)
    return wrapper

In python 3, below is to make *args and **kwargs into a dict (use OrderedDict for python < 3.6 to maintain dict orders):

from functools import wraps

def display_param(func):
    @wraps(func)
    def wrapper(*args, **kwargs):

        param = inspect.signature(func).parameters
        all_param = {
            k: args[n] if n < len(args) else v.default
            for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
        }
        all_param .update(kwargs)
        print(all_param)

        return func(**all_param)
    return wrapper

回答 12

inspect.signature非常慢 最快的方法是

def f(a, b=1, *args, c, d=1, **kwargs):
   pass

f_code = f.__code__
f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')

inspect.signature is very slow. Fastest way is

def f(a, b=1, *args, c, d=1, **kwargs):
   pass

f_code = f.__code__
f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')

回答 13

要稍微更新Brian的答案,现在inspect.signature可以在较旧的python版本中使用它的一个很好的反向端口:funcsigs。所以我个人的喜好会

try:  # python 3.3+
    from inspect import signature
except ImportError:
    from funcsigs import signature

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

有趣的是,如果您有兴趣玩Signature对象甚至动态创建带有随机签名的函数,您可以看看我的makefun项目。

To update a little bit Brian’s answer, there is now a nice backport of inspect.signature that you can use in older python versions: funcsigs. So my personal preference would go for

try:  # python 3.3+
    from inspect import signature
except ImportError:
    from funcsigs import signature

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

For fun, if you’re interested in playing with Signature objects and even creating functions with random signatures dynamically you can have a look at my makefun project.


回答 14

怎么样dir()vars()现在?

似乎完全可以简单地完成被要求的工作……

必须在功能范围内调用。

但请注意,它将返回所有局部变量,因此请确保在函数的开始处进行此操作(如果需要)。

还要注意,正如注释中指出的那样,这不允许在范围之外进行操作。因此,OP的情况不完全相同,但仍与问题标题匹配。因此,我的答案。

What about dir() and vars() now?

Seems doing exactly what is being asked super simply…

Must be called from within the function scope.

But be wary that it will return all local variables so be sure to do it at the very beginning of the function if needed.

Also note that, as pointed out in the comments, this doesn’t allow it to be done from outside the scope. So not exactly OP’s scenario but still matches the question title. Hence my answer.


Python装饰器有哪些常见用途?[关闭]

问题:Python装饰器有哪些常见用途?[关闭]

尽管我喜欢将自己视为一个相当称职的Python编码器,但我从来没有想到过的语言的一方面是装饰器。

我知道它们是什么(表面上),我已经阅读了有关堆栈溢出的教程,示例和问题,而且我了解语法,可以编写自己的语法,偶尔使用@classmethod和@staticmethod,但是使用装饰器来解决我自己的Python代码中的问题。我从来没有遇到过这样的问题,我想:“嗯……这看起来像是装饰工的工作!”

因此,我想知道你们是否可以提供一些示例,说明您在自己的程序中使用装饰器的位置,并希望我会收到“ A-ha!”字样。片刻,他们。

While I like to think of myself as a reasonably competent Python coder, one aspect of the language I’ve never been able to grok is decorators.

I know what they are (superficially), I’ve read tutorials, examples, questions on Stack Overflow, and I understand the syntax, can write my own, occasionally use @classmethod and @staticmethod, but it never occurs to me to use a decorator to solve a problem in my own Python code. I never encounter a problem where I think, “Hmm…this looks like a job for a decorator!”

So, I’m wondering if you guys might offer some examples of where you’ve used decorators in your own programs, and hopefully I’ll have an “A-ha!” moment and get them.


回答 0

我将装饰器主要用于计时目的

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...

I use decorators mainly for timing purposes

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...

回答 1

我已使用它们进行同步。

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

正如评论中指出的那样,从Python 2.5开始,您可以将with语句与threading.Lock(或multiprocessing.Lock从2.6版本开始)对象结合使用, 以将装饰器的实现简化为:

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

无论如何,您都可以这样使用它:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

基本上,它只是将lock.acquire()/ lock.release()放在函数调用的任一侧。

I’ve used them for synchronization.

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

As pointed out in the comments, since Python 2.5 you can use a with statement in conjunction with a threading.Lock (or multiprocessing.Lock since version 2.6) object to simplify the decorator’s implementation to just:

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

Regardless, you then use it like this:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

Basically it just puts lock.acquire() / lock.release() on either side of the function call.


回答 2

我使用装饰器进行类型检查参数,这些参数通过一些RMI传递给我的Python方法。因此,与其重复相同的参数计数,不如一次又一次地引发异常。

例如,代替:

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

我只声明:

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

accepts()为我完成所有工作。

I use decorators for type checking parameters which are passed to my Python methods via some RMI. So instead of repeating the same parameter counting, exception-raising mumbo-jumbo again and again.

For example, instead of:

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

I just declare:

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

and accepts() does all the work for me.


回答 3

装饰器用于您要透明“包装”其他功能的任何物品。

Django使用它们将“需要登录”功能包装在视图功能上,以及注册过滤器功能

您可以使用类装饰器将命名日志添加到类

您可以“掌握”现有类或功能行为的任何足够通用的功能,对于装饰来说都是公平的。

PEP 318指向的Python-Dev新闻组上讨论了用例-函数和方法的装饰器

Decorators are used for anything that you want to transparently “wrap” with additional functionality.

Django uses them for wrapping “login required” functionality on view functions, as well as for registering filter functions.

You can use class decorators for adding named logs to classes.

Any sufficiently generic functionality that you can “tack on” to an existing class or function’s behavior is fair game for decoration.

There’s also a discussion of use cases on the Python-Dev newsgroup pointed to by PEP 318 — Decorators for Functions and Methods.


回答 4

对于鼻子测试,您可以编写一个装饰器,该装饰器为单元测试功能或方法提供几组参数:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

For nosetests, you can write a decorator that supplies a unit test function or method with several sets of parameters:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

回答 5

Twisted库结合使用装饰器和生成器,给人一种异步函数是同步的错觉。例如:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

使用此功能,原本可以分解成许多小回调函数的代码可以很自然地作为一个单独的块编写,从而使理解和维护变得更加容易。

The Twisted library uses decorators combined with generators to give the illusion that an asynchronous function is synchronous. For example:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

Using this, code that would have been broken up into a ton of little callback functions can be written quite naturally as a single block, making it a lot easier to understand and maintain.


回答 6

当然,一种明显的用途是用于日志记录:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()

One obvious use is for logging, of course:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()

回答 7

我主要将它们用于调试(包装打印其参数和结果的函数)和验证(例如,检查参数的类型是否正确,或者对于Web应用程序,如果用户具有足够的特权来调用特定参数方法)。

I use them mainly for debugging (wrapper around a function that prints its arguments and result) and verification (e.g. to check if an argument is of correct type or, in the case of web application, if the user has sufficient privileges to call a particular method).


回答 8

我正在使用以下装饰器来使函数成为线程安全的。它使代码更具可读性。它几乎与John Fouhy提出的类似,但是区别在于,一个函数可以处理单个函数,因此无需显式创建锁对象。

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var

I am using the following decorator for making a function threadsafe. It makes the code more readable. It is almost similar to the one proposed by John Fouhy but the difference is that one work on a single function and that there is no need to create a lock object explicitely.

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var

回答 9

装饰器用于定义函数的属性或用作更改函数的样板。对于他们来说,返回完全不同的功能是可能的,但违反直觉。在这里查看其他响应,似乎最常见的用途之一是限制其他过程的范围-日志记录,性能分析,安全检查等。

CherryPy使用对象分派将URL与对象以及方法进行匹配。这些方法上的装饰器会发出信号,表明是否甚至允许 CherryPy 使用这些方法。例如,根据本教程改编而成:

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())

Decorators are used either to define a function’s properties or as boilerplate that alters it; it’s possible but counter-intuitive for them to return completely different functions. Looking at the other responses here, it seems like one of the most common uses is to limit the scope of some other process – be it logging, profiling, security checks, etc.

CherryPy uses object-dispatching to match URLs to objects and, eventually, methods. Decorators on those methods signal whether or not CherryPy is even allowed to use those methods. For example, adapted from the tutorial:

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())

回答 10

我最近在社交网络Web应用程序上使用它们。对于社区/小组,我应该授予成员资格以创建新的讨论并回复您必须是该特定小组成员的消息。因此,我写了一个装饰器,@membership_required并把它放在我认为需要的地方。

I used them recently, while working on social networking web application. For Community/Groups, I was supposed to give membership authorization to create new discussion and reply to a message you have to be the member of that particular group. So, I wrote a decorator @membership_required and put that where I required in my view.


回答 11

我用这个装饰器来修复参数

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

当我重构某些函数需要传递参数“ wanN”时编写的此代码,但是在我的旧代码中,我仅传递了N或’N’

I use this decorator to fix parameter

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

this written when I refactor some functions need to passed argument “wanN” but in my old codes, I passed N or ‘N’ only


回答 12

装饰器可用于轻松创建函数方法变量。

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1

Decorator can be used to easily create function method variables.

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1

带参数的装饰器?

问题:带参数的装饰器?

我在装饰器传递变量’insurance_mode’时遇到问题。我可以通过以下装饰器语句来做到这一点:

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

但不幸的是,该声明不起作用。也许也许有更好的方法来解决此问题。

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function

I have a problem with the transfer of variable ‘insurance_mode’ by the decorator. I would do it by the following decorator statement:

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

but unfortunately, this statement does not work. Perhaps maybe there is better way to solve this problem.

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function

回答 0

带参数的装饰器的语法有些不同-带参数的装饰器应返回一个函数,该函数将接受一个函数并返回另一个函数。因此,它实际上应该返回一个普通的装饰器。有点混乱吧?我的意思是:

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

在这里,您可以阅读有关该主题的更多信息-也可以使用可调用对象来实现此目的,这也在那里进行了说明。

The syntax for decorators with arguments is a bit different – the decorator with arguments should return a function that will take a function and return another function. So it should really return a normal decorator. A bit confusing, right? What I mean is:

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

Here you can read more on the subject – it’s also possible to implement this using callable objects and that is also explained there.


回答 1

编辑:要深入了解装饰者的心理模型,请看一下这个很棒的Pycon Talk。非常值得30分钟。

考虑带有参数的装饰器的一种方法是

@decorator
def foo(*args, **kwargs):
    pass

转换为

foo = decorator(foo)

因此,如果装饰者有参数,

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

转换为

foo = decorator_with_args(arg)(foo)

decorator_with_args 是一个接受自定义参数并返回实际装饰器的函数(将应用于装饰器函数)。

我使用带有局部选项的简单技巧来使我的装饰器变得容易

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

更新:

以上,foo成为real_decorator(foo)

装饰函数的一种效果是,名称foo在装饰器声明时被覆盖。foo被的返回值“覆盖” real_decorator。在这种情况下,一个新的功能对象。

的所有foo元数据都将被覆盖,特别是docstring和函数名称。

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

functools.wraps为我们提供了一种方便的方法,可将文档字符串和名称“提升”为返回的函数。

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

Edit : for an in-depth understanding of the mental model of decorators, take a look at this awesome Pycon Talk. well worth the 30 minutes.

One way of thinking about decorators with arguments is

@decorator
def foo(*args, **kwargs):
    pass

translates to

foo = decorator(foo)

So if the decorator had arguments,

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

translates to

foo = decorator_with_args(arg)(foo)

decorator_with_args is a function which accepts a custom argument and which returns the actual decorator (that will be applied to the decorated function).

I use a simple trick with partials to make my decorators easy

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

Update:

Above, foo becomes real_decorator(foo)

One effect of decorating a function is that the name foo is overridden upon decorator declaration. foo is “overridden” by whatever is returned by real_decorator. In this case, a new function object.

All of foo‘s metadata is overridden, notably docstring and function name.

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

functools.wraps gives us a convenient method to “lift” the docstring and name to the returned function.

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

回答 2

我想展示一个想法,恕我直言,非常优雅。t.dubrownik提出的解决方案显示了一种始终不变的模式:无论装饰器做什么,都需要三层包装器。

所以我认为这是元装饰器的工作,即装饰器的装饰器。装饰器是一个函数,它实际上可以用作带有参数的常规装饰器:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

可以将其应用于常规装饰器以添加参数。例如,假设我们有一个装饰器,它将一个函数的结果加倍:

def double(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

通过@parametrized我们可以构建@multiply具有参数的通用装饰器

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

通常,a的第一个参数 装饰器参数是函数,而其余参数将对应于参数化装饰器的参数。

一个有趣的用法示例可以是类型安全的断言修饰符:

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

最后一点:这里我没有使用functools.wraps包装函数,但是我建议您一直使用它。

I’d like to show an idea which is IMHO quite elegant. The solution proposed by t.dubrownik shows a pattern which is always the same: you need the three-layered wrapper regardless of what the decorator does.

So I thought this is a job for a meta-decorator, that is, a decorator for decorators. As a decorator is a function, it actually works as a regular decorator with arguments:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

This can be applied to a regular decorator in order to add parameters. So for instance, say we have the decorator which doubles the result of a function:

def double(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

With @parametrized we can build a generic @multiply decorator having a parameter

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

Conventionally the first parameter of a parametrized decorator is the function, while the remaining arguments will correspond to the parameter of the parametrized decorator.

An interesting usage example could be a type-safe assertive decorator:

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

A final note: here I’m not using functools.wraps for the wrapper functions, but I would recommend using it all the times.


回答 3

这是t.dubrownik的答案的略微修改版本。为什么?

  1. 作为常规模板,您应该从原始函数返回返回值。
  2. 这会更改函数的名称,这可能会影响其他修饰符/代码。

因此使用@functools.wraps()

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator

Here is a slightly modified version of t.dubrownik’s answer. Why?

  1. As a general template, you should return the return value from the original function.
  2. This changes the name of the function, which could affect other decorators / code.

So use @functools.wraps():

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator

回答 4

我想您的问题是将参数传递给装饰器。这有点棘手,并不简单。

这是如何执行此操作的示例:

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

印刷品:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

有关更多详细信息,请参见Bruce Eckel的文章。

I presume your problem is passing arguments to your decorator. This is a little tricky and not straightforward.

Here’s an example of how to do this:

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

Prints:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

See Bruce Eckel’s article for more details.


回答 5

def decorator(argument):
    def real_decorator(function):
        def wrapper(*args):
            for arg in args:
                assert type(arg)==int,f'{arg} is not an interger'
            result = function(*args)
            result = result*argument
            return result
        return wrapper
    return real_decorator

装饰器的用法

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

然后

adder(2,3)

产生

10

adder('hi',3)

产生

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)

<ipython-input-140-d3420c248ebd> in wrapper(*args)
      3         def wrapper(*args):
      4             for arg in args:
----> 5                 assert type(arg)==int,f'{arg} is not an interger'
      6             result = function(*args)
      7             result = result*argument

AssertionError: hi is not an interger
def decorator(argument):
    def real_decorator(function):
        def wrapper(*args):
            for arg in args:
                assert type(arg)==int,f'{arg} is not an interger'
            result = function(*args)
            result = result*argument
            return result
        return wrapper
    return real_decorator

Usage of the decorator

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

Then the

adder(2,3)

produces

10

but

adder('hi',3)

produces

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)

<ipython-input-140-d3420c248ebd> in wrapper(*args)
      3         def wrapper(*args):
      4             for arg in args:
----> 5                 assert type(arg)==int,f'{arg} is not an interger'
      6             result = function(*args)
      7             result = result*argument

AssertionError: hi is not an interger

回答 6

这是用于函数装饰器的模板,该模板不需要提供()任何参数即可:

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator

下面是一个示例:

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

This is a template for a function decorator that does not require () if no parameters are to be given:

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator

an example of this is given below:

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

回答 7

在我的实例中,我决定通过单行lambda解决此问题,以创建一个新的装饰器函数:

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

执行后,将打印:

Finished!
All Done!

也许没有其他解决方案可扩展,但是为我工作。

In my instance, I decided to solve this via a one-line lambda to create a new decorator function:

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

When executed, this prints:

Finished!
All Done!

Perhaps not as extensible as other solutions, but worked for me.


回答 8

编写一个可以使用和不使用参数的装饰器是一个挑战,因为Python在这两种情况下期望完全不同的行为!许多答案都试图解决此问题,以下是@ norok2对答案的改进。具体来说,这种变化消除了对的使用locals()

遵循@ norok2给出的相同示例:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

玩这个代码

要注意的是,用户必须提供键,值对参数而不是位置参数,并且保留第一个参数。

Writing a decorator that works with and without parameter is a challenge because Python expects completely different behavior in these two cases! Many answers have tried to work around this and below is an improvement of answer by @norok2. Specifically, this variation eliminates the use of locals().

Following the same example as given by @norok2:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

Play with this code.

The catch is that the user must supply key,value pairs of parameters instead of positional parameters and the first parameter is reserved.


回答 9

众所周知,以下两段代码几乎等效:

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

一个常见的错误是认为它@只是隐藏了最左边的参数。

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

如果上面是这样的话,编写装饰器会容易得多@。不幸的是,这不是事情的完成方式。


考虑一个装饰器Wait,它会破坏程序执行几秒钟。如果您未通过等待时间,则默认值为1秒。用例如下所示。

##################################################
@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)

Wait具有参数(例如)时@Wait(3),将其他任何事情发生之前Wait(3) 执行调用。

也就是说,以下两段代码是等效的

@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)

这是个问题。

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

一种解决方案如下所示:

让我们从创建以下类开始DelayedDecorator

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)

现在我们可以编写如下内容:

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

注意:

  • dec 不接受多个参数。
  • dec 仅接受要包装的功能。

    导入检查类PolyArgDecoratorMeta(type):def 调用(等待,* args,** kwargs):尝试:arg_count = len(args)if(arg_count == 1):如果callable(args [0]):SuperClass = inspect。 getmro(PolyArgDecoratorMeta)[1] r =超类。呼叫(Wait,args [0])否则:r = DelayedDecorator(等待,* args,** kwargs)否则:r = DelayedDecorator(等待,* args,** kwargs)最后:通过return r

    导入时间类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 

以下两段代码是等效的:

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

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

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

我们可以"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")

最后说明

它可能看起来像一个大量的代码,但你不必写类DelayedDecoratorPolyArgDecoratorMeta每一个时间。您唯一需要亲自编写类似以下内容的代码,这很短:

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

It is well known that the following two pieces of code are nearly equivalent:

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

A common mistake is to think that @ simply hides the leftmost argument.

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

It would be much easier to write decorators if the above is how @ worked. Unfortunately, that’s not the way things are done.


Consider a decorator Waitwhich haults program execution for a few seconds. If you don’t pass in a Wait-time then the default value is 1 seconds. 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.

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:
    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 only accepts the function to be wrapped.

    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:

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. 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

回答 10

定义此“ decoratorize函数”以生成定制的装饰器函数:

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

使用这种方式:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...

define this “decoratorize function” to generate customized decorator function:

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

use it this way:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...

回答 11

上面的好答案。此示例还说明了@wraps,它从原始函数中获取doc字符串和函数名,并将其应用于新的包装版本:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

印刷品:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello

Great answers above. This one also illustrates @wraps, which takes the doc string and function name from the original function and applies it to the new wrapped version:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

Prints:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello

回答 12

如果函数和装饰器都必须接受参数,则可以采用以下方法。

例如,有一个名为的装饰器decorator1,它接受一个参数

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

现在,如果decorator1参数必须是动态的,或者在调用函数时传递的,

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

在上面的代码中

  • seconds 是为 decorator1
  • a, b 是…的论点 func1

In case both the function and the decorator have to take arguments you can follow the below approach.

For example there is a decorator named decorator1 which takes an argument

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

Now if the decorator1 argument has to be dynamic, or passed while calling the function,

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

In the above code

  • seconds is the argument for decorator1
  • a, b are the arguments of func1

@property装饰器如何工作?

问题:@property装饰器如何工作?

我想了解内置函数的property工作原理。令我感到困惑的是,property它还可以用作装饰器,但是仅当用作内置函数时才接受参数,而不能用作装饰器。

这个例子来自文档

class C(object):
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

property的论点是getxsetxdelx和文档字符串。

在下面的代码中property用作装饰器。它的对象是x函数,但是在上面的代码中,参数中没有对象函数的位置。

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

并且,x.setterx.deleter装饰器是如何创建的?我很困惑。

I would like to understand how the built-in function property works. What confuses me is that property can also be used as a decorator, but it only takes arguments when used as a built-in function and not when used as a decorator.

This example is from the documentation:

class C(object):
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

property‘s arguments are getx, setx, delx and a doc string.

In the code below property is used as decorator. The object of it is the x function, but in the code above there is no place for an object function in the arguments.

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

And, how are the x.setter and x.deleter decorators created? I am confused.


回答 0

property()函数返回一个特殊的描述符对象

>>> property()
<property object at 0x10ff07940>

正是这种对象有额外的方法:

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

这些充当装饰。他们返回一个新的属性对象:

>>> property().getter(None)
<property object at 0x10ff079f0>

那是旧对象的副本,但是替换了其中一个功能。

请记住,@decorator语法只是语法糖。语法:

@property
def foo(self): return self._foo

确实与

def foo(self): return self._foo
foo = property(foo)

因此foo该函数被替换property(foo),我们在上面看到的是一个特殊的对象。然后,当您使用时@foo.setter(),您正在做的就是调用property().setter上面显示的方法,该方法将返回该属性的新副本,但是这次将setter函数替换为装饰方法。

下面的序列还通过使用那些装饰器方法创建了一个全开属性。

首先,我们property仅使用getter 创建一些函数和一个对象:

>>> def getter(self): print('Get!')
... 
>>> def setter(self, value): print('Set to {!r}!'.format(value))
... 
>>> def deleter(self): print('Delete!')
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

接下来,我们使用该.setter()方法添加setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

最后,我们使用以下.deleter()方法添加删除器:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

最后但并非最不重要的一点是,该property对象充当描述符对象,因此它具有和.__get__(),可以.__set__().__delete__()实例属性的获取,设置和删除方法挂钩:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Descriptor Howto包括以下类型的纯Python示例实现property()

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

The property() function returns a special descriptor object:

>>> property()
<property object at 0x10ff07940>

It is this object that has extra methods:

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

These act as decorators too. They return a new property object:

>>> property().getter(None)
<property object at 0x10ff079f0>

that is a copy of the old object, but with one of the functions replaced.

Remember, that the @decorator syntax is just syntactic sugar; the syntax:

@property
def foo(self): return self._foo

really means the same thing as

def foo(self): return self._foo
foo = property(foo)

so foo the function is replaced by property(foo), which we saw above is a special object. Then when you use @foo.setter(), what you are doing is call that property().setter method I showed you above, which returns a new copy of the property, but this time with the setter function replaced with the decorated method.

The following sequence also creates a full-on property, by using those decorator methods.

First we create some functions and a property object with just a getter:

>>> def getter(self): print('Get!')
... 
>>> def setter(self, value): print('Set to {!r}!'.format(value))
... 
>>> def deleter(self): print('Delete!')
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

Next we use the .setter() method to add a setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Last we add a deleter with the .deleter() method:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Last but not least, the property object acts as a descriptor object, so it has .__get__(), .__set__() and .__delete__() methods to hook into instance attribute getting, setting and deleting:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

The Descriptor Howto includes a pure Python sample implementation of the property() type:

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

回答 1

文档说这只是创建只读属性的捷径。所以

@property
def x(self):
    return self._x

相当于

def getx(self):
    return self._x
x = property(getx)

Documentation says it’s just a shortcut for creating readonly properties. So

@property
def x(self):
    return self._x

is equivalent to

def getx(self):
    return self._x
x = property(getx)

回答 2

这是如何@property实现的最小示例:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

否则,将word保留方法而不是属性。

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'

Here is a minimal example of how @property can be implemented:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

Otherwise word remains a method instead of a property.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'

回答 3

第一部分很简单:

@property
def x(self): ...

是相同的

def x(self): ...
x = property(x)
  • 反过来,这是property仅使用getter 创建a的简化语法。

下一步将使用设置器和删除器扩展此属性。并通过适当的方法来实现:

@x.setter
def x(self, value): ...

返回一个新属性,该属性继承了旧属性x以及给定的setter的所有内容。

x.deleter 以相同的方式工作。

The first part is simple:

@property
def x(self): ...

is the same as

def x(self): ...
x = property(x)
  • which, in turn, is the simplified syntax for creating a property with just a getter.

The next step would be to extend this property with a setter and a deleter. And this happens with the appropriate methods:

@x.setter
def x(self, value): ...

returns a new property which inherits everything from the old x plus the given setter.

x.deleter works the same way.


回答 4

以下内容:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

是相同的:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

是相同的:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

是相同的:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

等同于:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

This following:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Is the same as:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

Is the same as:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

Is the same as:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

Which is the same as :

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

回答 5

下面是另一个示例,该示例在@property需要重构代码的情况下如何提供帮助(从此处进行总结):

假设您创建了一个Money这样的类:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

并且用户根据他/她使用的此类创建一个库

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

现在,让我们假设您决定更改您的Money类并摆脱dollarscents属性,而是决定仅跟踪总分:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

如果上述用户现在尝试像以前一样运行他/她的库

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

这会导致错误

AttributeError:“ Money”对象没有属性“ dollars”

也就是说,现在大家谁依赖于原始的手段Money类将不得不改变所有代码行,其中dollarscents使用可以是非常痛苦……那么,怎么会这样避免?通过使用@property

就是那样:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

现在我们从图书馆打电话时

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

它会按预期工作,我们不必在库中更改任何代码!实际上,我们甚至不必知道我们依赖的库已更改。

setter可以正常工作:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

您也@property可以在抽象类中使用。我在这里举一个最小的例子。

Below is another example on how @property can help when one has to refactor code which is taken from here (I only summarize it below):

Imagine you created a class Money like this:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

and an user creates a library depending on this class where he/she uses e.g.

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Now let’s suppose you decide to change your Money class and get rid of the dollars and cents attributes but instead decide to only track the total amount of cents:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

If the above mentioned user now tries to run his/her library as before

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

it will result in an error

AttributeError: ‘Money’ object has no attribute ‘dollars’

That means that now everyone who relies on your original Money class would have to change all lines of code where dollars and cents are used which can be very painful… So, how could this be avoided? By using @property!

That is how:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

when we now call from our library

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

it will work as expected and we did not have to change a single line of code in our library! In fact, we would not even have to know that the library we depend on changed.

Also the setter works fine:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

You can use @property also in abstract classes; I give a minimal example here.


回答 6

我在这里阅读了所有文章,并意识到我们可能需要一个真实的例子。为什么实际上我们有@property?因此,考虑使用身份验证系统的Flask应用。您在中声明模型用户models.py

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

在这段代码中,我们password使用了“隐藏”属性,当您尝试直接访问它时@property,该属性会触发AttributeError断言,而我们使用@ property.setter来设置实际的实例变量password_hash

现在,auth/views.py我们可以实例化一个用户:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

password用户填写表单时,该属性来自注册表单。密码确认发生在前端EqualTo('password', message='Passwords must match')(如果您想知道,但这是与Flask表单相关的其他主题)。

我希望这个例子会有用

I read all the posts here and realized that we may need a real life example. Why, actually, we have @property? So, consider a Flask app where you use authentication system. You declare a model User in models.py:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

In this code we’ve “hidden” attribute password by using @property which triggers AttributeError assertion when you try to access it directly, while we used @property.setter to set the actual instance variable password_hash.

Now in auth/views.py we can instantiate a User with:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

Notice attribute password that comes from a registration form when a user fills the form. Password confirmation happens on the front end with EqualTo('password', message='Passwords must match') (in case if you are wondering, but it’s a different topic related Flask forms).

I hope this example will be useful


回答 7

那里的很多人都清楚了这一点,但这是我一直在寻找的直接点。我觉得从@property装饰器开始很重要。例如:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

函数“ get_config()”的调用将像这样工作。

util = UtilityMixin()
print(util.get_config)

如果您注意到我没有使用“()”括号来调用该函数。这是我在搜索@property装饰器的基本内容。这样您就可以像使用变量一样使用函数。

This point is been cleared by many people up there but here is a direct point which I was searching. This is what I feel is important to start with the @property decorator. eg:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

The calling of function “get_config()” will work like this.

util = UtilityMixin()
print(util.get_config)

If you notice I have not used “()” brackets for calling the function. This is the basic thing which I was searching for the @property decorator. So that you can use your function just like a variable.


回答 8

让我们从Python装饰器开始。

Python装饰器是一个函数,可以帮助向已经定义的函数添加一些其他功能。

在Python中,一切都是对象。Python中的函数是一流的对象,这意味着它们可以被变量引用,添加到列表中,作为参数传递给另一个函数等。

考虑以下代码片段。

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

在这里,我们可以说装饰器函数修改了我们的say_hello函数,并在其中添加了一些额外的代码行。

装饰器的Python语法

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

最后,让我们结束一个案例案例,但在此之前,让我们先讨论一些糟糕的原则。

在许多面向对象的编程语言中都使用getter和setter来确保数据封装的原理(被视为将数据与对这些数据进行操作的方法捆绑在一起)。

这些方法当然是用于获取数据的吸气剂和用于更改数据的设置器。

根据此原理,将一个类的属性设为私有,以隐藏它们并保护它们免受其他代码的侵害。

是的,@ property基本上是使用getter和setterpythonic方法。

Python有一个伟大的概念,称为属性,它使面向对象的程序员的生活变得更加简单。

让我们假设您决定创建一个可以存储摄氏温度的类。

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

重构代码,这是我们可以通过属性实现的方法。

在Python中,property()是一个内置函数,可创建并返回属性对象。

属性对象具有三种方法,getter(),setter()和delete()。

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

这里,

temperature = property(get_temperature,set_temperature)

本可以分解为

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

注意事项:

  • get_temperature仍然是属性而不是方法。

现在,您可以通过写入来获取温度值。

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

我们可以进一步继续,不要定义名称get_temperatureset_temperature,因为它们是不必要的,并污染类命名空间。

解决上述问题的pythonic方法是使用@property

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

注意事项-

  1. 用于获取值的方法以“ @property”修饰。
  2. 用作设置器的方法用“ @ temperature.setter”修饰,如果该函数被称为“ x”,则必须用“ @ x.setter”修饰。
  3. 我们用相同的名称和不同数量的参数“ def temperature(self)”和“ def temperature(self,x)”编写了“两个”方法。

如您所见,该代码绝对不太优雅。

现在,让我们谈谈一个现实的实用场景。

假设您设计的类如下:

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

现在,让我们进一步假设我们的类在客户中很受欢迎,并且他们开始在程序中使用它。他们对对象进行了各种分配。

有朝一日,一个值得信赖的客户来找我们,建议“ x”的值必须在0到1000之间,这确实是一个可怕的情况!

由于属性,这很容易:我们创建属性版本“ x”。

class OurClass:

    def __init__(self,x):
        self.x = x

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

很好,不是吗:您可以从可以想象到的最简单的实现开始,并且以后可以随意迁移到属性版本,而不必更改接口!因此,属性不仅仅是吸气剂和塞特剂的替代品!

您可以在此处检查此实现

Let’s start with Python decorators.

A Python decorator is a function that helps to add some additional functionalities to an already defined function.

In Python, everything is an object. Functions in Python are first-class objects which means that they can be referenced by a variable, added in the lists, passed as arguments to another function etc.

Consider the following code snippet.

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

Here, we can say that decorator function modified our say_hello function and added some extra lines of code in it.

Python syntax for decorator

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

Let’s Concluded everything than with a case scenario, but before that let’s talk about some oops priniciples.

Getters and setters are used in many object oriented programming languages to ensure the principle of data encapsulation(is seen as the bundling of data with the methods that operate on these data.)

These methods are of course the getter for retrieving the data and the setter for changing the data.

According to this principle, the attributes of a class are made private to hide and protect them from other code.

Yup, @property is basically a pythonic way to use getters and setters.

Python has a great concept called property which makes the life of an object-oriented programmer much simpler.

Let us assume that you decide to make a class that could store the temperature in degree Celsius.

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

Refactored Code, Here is how we could have achieved it with property.

In Python, property() is a built-in function that creates and returns a property object.

A property object has three methods, getter(), setter(), and delete().

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

Here,

temperature = property(get_temperature,set_temperature)

could have been broken down as,

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

Point To Note:

  • get_temperature remains a property instead of a method.

Now you can access the value of temperature by writing.

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

We can further go on and not define names get_temperature and set_temperature as they are unnecessary and pollute the class namespace.

The pythonic way to deal with the above problem is to use @property.

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

Points to Note –

  1. A method which is used for getting a value is decorated with “@property”.
  2. The method which has to function as the setter is decorated with “@temperature.setter”, If the function had been called “x”, we would have to decorate it with “@x.setter”.
  3. We wrote “two” methods with the same name and a different number of parameters “def temperature(self)” and “def temperature(self,x)”.

As you can see, the code is definitely less elegant.

Now,let’s talk about one real-life practical scenerio.

Let’s say you have designed a class as follows:

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

Now, let’s further assume that our class got popular among clients and they started using it in their programs, They did all kinds of assignments to the object.

And One fateful day, a trusted client came to us and suggested that “x” has to be a value between 0 and 1000, this is really a horrible scenario!

Due to properties it’s easy: We create a property version of “x”.

class OurClass:

    def __init__(self,x):
        self.x = x

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

This is great, isn’t it: You can start with the simplest implementation imaginable, and you are free to later migrate to a property version without having to change the interface! So properties are not just a replacement for getters and setter!

You can check this Implementation here


回答 9

property@property装饰器背后的一类。

您可以随时检查以下内容:

print(property) #<class 'property'>

我改写了示例,help(property)以显示@property语法

class C:
    def __init__(self):
        self._x=None

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

在功能上与property()语法相同:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

如您所见,我们使用该属性的方式没有什么不同。

为了回答这个问题,@property装饰器是通过property类实现的。


因此,问题是要对该property类进行一些解释。这行:

prop = property(g,s,d)

是初始化。我们可以这样重写它:

prop = property(fget=g,fset=s,fdel=d)

的含义fgetfsetfdel

 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

下图显示了我们从类中获得的三胞胎property

在此处输入图片说明

__get____set____delete__那里被覆盖。这是Python中描述符模式的实现。

通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法所覆盖。

我们还可以使用属性settergetterdeleter方法的功能绑定属性。检查下一个示例。s2该类的方法C会将属性设置为double

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x


    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      


c = C()
c.x="a"
print(c.x) # outputs "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"

property is a class behind @property decorator.

You can always check this:

print(property) #<class 'property'>

I rewrote the example from help(property) to show that the @property syntax

class C:
    def __init__(self):
        self._x=None

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

is functionally identical to property() syntax:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

There is no difference how we use the property as you can see.

To answer the question @property decorator is implemented via property class.


So, the question is to explain the property class a bit. This line:

prop = property(g,s,d)

Was the initialization. We can rewrite it like this:

prop = property(fget=g,fset=s,fdel=d)

The meaning of fget, fset and fdel:

 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

The next image shows the triplets we have, from the class property:

enter image description here

__get__, __set__, and __delete__ are there to be overridden. This is the implementation of the descriptor pattern in Python.

In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol.

We can also use property setter, getter and deleter methods to bind the function to property. Check the next example. The method s2 of the class C will set the property doubled.

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x


    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      


c = C()
c.x="a"
print(c.x) # outputs "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"

回答 10

可以通过两种方式声明属性。

  • 为属性创建getter,setter方法,然后将它们作为参数传递给属性函数
  • 使用@property装饰器。

您可以看一下我编写的有关python属性的一些示例。

A property can be declared in two ways.

  • Creating the getter, setter methods for an attribute and then passing these as argument to property function
  • Using the @property decorator.

You can have a look at few examples I have written about properties in python.


回答 11

最好的解释可以在这里找到:Python @Property Explained –如何使用和何时使用?(完整示例)Selva Prabhakaran | 发表于十一月5,2018

它帮助我理解了为什么不仅如此。

https://www.machinelearningplus.com/python/python-property/

The best explanation can be found here: Python @Property Explained – How to Use and When? (Full Examples) by Selva Prabhakaran | Posted on November 5, 2018

It helped me understand WHY not only HOW.

https://www.machinelearningplus.com/python/python-property/


回答 12

这是另一个示例:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

基本上,与C(object)示例相同,只是我改用x …我也不在__init中初始化 -…很好..我可以,但是可以删除它,因为__x被定义为一部分班上的…

输出为:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

如果我将init的self.x = 1234注释掉,则输出为:

[ Test Class ] Get x = None
[ x ] None

并且如果我在getter函数中将_default = None设置为_default = 0(因为所有的getter都应具有默认值,但不会被我所看到的属性值传递,因此您可以在此处定义它,以及它实际上还不错,因为您可以定义一次默认值并在所有地方使用它),即:def x(self,_default = 0):

[ Test Class ] Get x = 0
[ x ] 0

注意:getter逻辑只是为了让它操纵值以确保它被操纵-与print语句相同…

注意:我习惯了Lua,并且在调用单个函数时能够动态创建10个以上的助手,并且我在不使用属性的情况下为Python做了类似的事情,并且在一定程度上可以正常工作,但是,即使这些函数是在之前创建的被使用时,在创建它们之前有时仍会调用它们,这很奇怪,因为它不是以这种方式编码的。。。我更喜欢Lua元表的灵活性,而且我可以使用实际的setter / getters。而不是本质上直接访问变量…我确实喜欢用Python可以快速构建某些东西-例如gui程序。尽管没有大量其他库,虽然我正在设计的库可能无法实现-如果我在AutoHotkey中对其进行编码,则可以直接访问所需的dll调用,并且可以在Java,C#,C ++,

注意:此论坛中的代码输出已损坏-我必须在代码的第一部分中添加空格才能使其正常工作-复制/粘贴时,请确保将所有空格都转换为制表符…。我在Python中使用制表符,因为在10,000行的文件大小可以为512KB至1MB(带空格)和100至200KB(带制表符),这在文件大小和减少处理时间方面存在巨大差异。

还可以按用户调整选项卡-因此,如果您希望使用2个空格宽度,4个,8个空格或您可以做的任何事情,这意味着它对于有视力缺陷的开发人员来说是体贴的。

注意:由于论坛软件中的错误,该类中定义的所有功能均未正确缩进-如果复制/粘贴,请确保将其缩进

Here is another example:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

Basically, the same as the C( object ) example except I’m using x instead… I also don’t initialize in __init – … well.. I do, but it can be removed because __x is defined as part of the class….

The output is:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

and if I comment out the self.x = 1234 in init then the output is:

[ Test Class ] Get x = None
[ x ] None

and if I set the _default = None to _default = 0 in the getter function ( as all getters should have a default value but it isn’t passed in by the property values from what I’ve seen so you can define it here, and it actually isn’t bad because you can define the default once and use it everywhere ) ie: def x( self, _default = 0 ):

[ Test Class ] Get x = 0
[ x ] 0

Note: The getter logic is there just to have the value be manipulated by it to ensure it is manipulated by it – the same for the print statements…

Note: I’m used to Lua and being able to dynamically create 10+ helpers when I call a single function and I made something similar for Python without using properties and it works to a degree, but, even though the functions are being created before being used, there are still issues at times with them being called prior to being created which is strange as it isn’t coded that way… I prefer the flexibility of Lua meta-tables and the fact I can use actual setters / getters instead of essentially directly accessing a variable… I do like how quickly some things can be built with Python though – for instance gui programs. although one I am designing may not be possible without a lot of additional libraries – if I code it in AutoHotkey I can directly access the dll calls I need, and the same can be done in Java, C#, C++, and more – maybe I haven’t found the right thing yet but for that project I may switch from Python..

Note: The code output in this forum is broken – I had to add spaces to the first part of the code for it to work – when copy / pasting ensure you convert all spaces to tabs…. I use tabs for Python because in a file which is 10,000 lines the filesize can be 512KB to 1MB with spaces and 100 to 200KB with tabs which equates to a massive difference for file size, and reduction in processing time…

Tabs can also be adjusted per user – so if you prefer 2 spaces width, 4, 8 or whatever you can do it meaning it is thoughtful for developers with eye-sight deficits.

Note: All of the functions defined in the class aren’t indented properly because of a bug in the forum software – ensure you indent it if you copy / paste


回答 13

一句话:对我来说,对于Python 2.x,@property当我不继承自object

class A():
    pass

但在以下情况下有效:

class A(object):
    pass

对于Python 3,始终有效。

One remark: for me, for Python 2.x, @property didn’t work as advertised when I didn’t inherit from object:

class A():
    pass

but worked when:

class A(object):
    pass

for Python 3, worked always.


在Python中创建单例

问题:在Python中创建单例

这个问题不是为了讨论是否需要单例设计模式,是否是反模式,还是针对任何宗教战争,而是要讨论如何以最pythonic的方式在Python中最好地实现此模式。在这种情况下,我将“最pythonic”定义为表示它遵循“最少惊讶的原理”

我有多个将成为单例的类(我的用例用于记录器,但这并不重要)。当我可以简单地继承或修饰时,我不希望增加gumph来使几个类杂乱无章。

最佳方法:


方法1:装饰器

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

优点

  • 装饰器的添加方式通常比多重继承更直观。

缺点

  • 使用MyClass()创建的对象将是真正的单例对象,而MyClass本身是一个函数,而不是类,因此您不能从中调用类方法。也就m = MyClass(); n = MyClass(); o = type(n)();这样m == n && m != o && n != o

方法2:一个基类

class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

优点

  • 这是一个真正的课堂

缺点

  • 多重继承-好!__new__从第二个基类继承期间可能被覆盖?人们必须思考的超出了必要。

方法3:元类

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

优点

  • 这是一个真正的课堂
  • 自动神奇地涵盖继承
  • 利用__metaclass__它的正确用途(使我意识到这一点)

缺点

  • 有吗

方法4:装饰器返回具有相同名称的类

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

优点

  • 这是一个真正的课堂
  • 自动神奇地涵盖继承

缺点

  • 创建每个新类没有开销吗?在这里,我们为希望创建单例的每个类创建两个类。虽然这对我来说很好,但我担心这可能无法扩展。当然,要扩展这种模式是否太容易了还有争议。
  • _sealed属性的重点是什么
  • 无法使用调用基类上同名的方法,super()因为它们会递归。这意味着您无法自定义__new__,也无法将需要调用的类作为子类__init__

方法5:一个模块

一个模块文件 singleton.py

优点

  • 简单胜于复杂

缺点

This question is not for the discussion of whether or not the singleton design pattern is desirable, is an anti-pattern, or for any religious wars, but to discuss how this pattern is best implemented in Python in such a way that is most pythonic. In this instance I define ‘most pythonic’ to mean that it follows the ‘principle of least astonishment’.

I have multiple classes which would become singletons (my use-case is for a logger, but this is not important). I do not wish to clutter several classes with added gumph when I can simply inherit or decorate.

Best methods:


Method 1: A decorator

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

Pros

  • Decorators are additive in a way that is often more intuitive than multiple inheritance.

Cons

  • While objects created using MyClass() would be true singleton objects, MyClass itself is a a function, not a class, so you cannot call class methods from it. Also for m = MyClass(); n = MyClass(); o = type(n)(); then m == n && m != o && n != o

Method 2: A base class

class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

Pros

  • It’s a true class

Cons

  • Multiple inheritance – eugh! __new__ could be overwritten during inheritance from a second base class? One has to think more than is necessary.

Method 3: A metaclass

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

Pros

  • It’s a true class
  • Auto-magically covers inheritance
  • Uses __metaclass__ for its proper purpose (and made me aware of it)

Cons

  • Are there any?

Method 4: decorator returning a class with the same name

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

Pros

  • It’s a true class
  • Auto-magically covers inheritance

Cons

  • Is there not an overhead for creating each new class? Here we are creating two classes for each class we wish to make a singleton. While this is fine in my case, I worry that this might not scale. Of course there is a matter of debate as to whether it aught to be too easy to scale this pattern…
  • What is the point of the _sealed attribute
  • Can’t call methods of the same name on base classes using super() because they will recurse. This means you can’t customize __new__ and can’t subclass a class that needs you to call up to __init__.

Method 5: a module

a module file singleton.py

Pros

  • Simple is better than complex

Cons


回答 0

使用元类

我建议使用方法2,但最好使用元类而不是基类。这是一个示例实现:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(object):
    __metaclass__ = Singleton

或在Python3中

class Logger(metaclass=Singleton):
    pass

如果要在__init__每次调用类时运行,请添加

        else:
            cls._instances[cls].__init__(*args, **kwargs)

对中的if陈述Singleton.__call__

关于元类的几句话。元类是类的类 ; 也就是说,类是其元类实例。您可以使用来找到Python中对象的元类type(obj)。普通的新式类是类型typeLogger上面代码中的将会是type class 'your_module.Singleton',就像的(唯一的)实例Logger将是type一样class 'your_module.Logger'。当你调用记录仪与Logger(),Python的首先要求的元类LoggerSingleton,做什么,允许实例创建要捷足先登。此过程与Python __getattr__通过执行以下操作引用类的一个属性时调用类来询问类的方法相同:myclass.attribute

元类从本质上决定了类定义的含义以及如何实现该定义。参见例如http://code.activestate.com/recipes/498149/,它实质上是struct使用元类在Python中重新创建C风格的。线程元类的一些(具体)用例是什么?还提供了一些示例,它们通常似乎与声明性编程有关,尤其是在ORM中使用的声明性编程。

在这种情况下,如果您使用方法2,并且子类定义了一个__new__方法,则每次调用都会执行SubClassOfSingleton()该方法-因为它负责调用返回存储实例的方法。对于元类,仅在创建唯一实例时才调用一次。您想自定义调用类的含义,该类的类型决定。

通常,使用元类实现单例是有意义的。单例很特别,因为它只能创建一次,而元类是自定义类创建的方式。如果需要以其他方式自定义单例类定义,则使用元类可以提供更多控制权

您的单例不需要多重继承(因为元类不是基类),但是对于使用多重继承的已创建类的子类,您需要确保单例类是第一个/最左边的一个具有重新定义的元类的类。__call__这不太可能成为问题。实例dict 不在实例的命名空间中,因此不会意外覆盖它。

您还将听到单例模式违反了“单一责任原则”-每个类只能一件事。这样,您就不必担心如果需要更改另一代码,便会弄乱代码要做的一件事,因为它们是分开封装的。元类实现通过了此测试。元类负责执行模式,创建的类和子类无需知道它们是单例。正如您在“ MyClass本身是一个函数而不是一个类,因此您无法从中调用类方法”中指出的那样,方法#1未能通过该测试。

Python 2和3兼容版本

编写适用于Python2和3的东西需要使用稍微复杂一些的方案。由于元类通常是type的子类type,因此可以使用一个在运行时动态创建中介基类并将其作为元类,然后将用作公共Singleton基类的基类。如下所示,这比做起来难解释。

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

具有讽刺意味的是,这种方法使用子类来实现元类。一个可能的优点是,与纯元类不同,isinstance(inst, Singleton)它将返回True

更正

在另一个主题上,您可能已经注意到了这一点,但是原始文章中的基类实现是错误的。_instances需要在类引用,您需要使用super()递归,并且__new__实际上是一个静态方法,您必须将类传递给,而不是类方法,因为尚未在其上创建实际的类叫做。所有这些对于元类实现也是正确的。

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

室内设计师返校

我本来是在写评论,但评论太长了,因此我将在此处添加。方法4比其他装饰器版本更好,但是它的代码比单例所需的代码更多,并且不清楚它的功能。

主要问题源于该类是它自己的基类。首先,让一个类成为几乎完全相同的类的子类不是很奇怪__class__吗?这也意味着你不能定义调用同名的方法对它们的基类的任何方法super(),因为他们会重复。这意味着您的类无法自定义__new__,并且不能从需要对其__init__调用的任何类派生。

何时使用单例模式

您的用例是想要使用单例的更好示例之一。您在其中一项评论中说:“对我而言,伐木一直是Singletons的自然选择。” 你说的

人们说单身人士很糟糕,最常见的原因是他们是隐性的共享状态。虽然全局变量和顶级模块导入是显式共享状态,但通常会实例化传递的其他对象。这是一个好点,但有两个exceptions

第一个,并且在各个地方都被提及的,是单例是恒定的。全局常数(尤其是枚举)的使用已被广泛接受,并被认为是明智的,因为无论如何,任何用户都无法为其他任何用户弄乱它们。对于恒定的单例也同样如此。

第二个exceptions(相反,它被忽略了)是相反的-当单例仅仅是数据接收器,而不是数据源(直接或间接)时。这就是为什么记录器感觉单例的“自然”使用。由于各种用户没有以其他用户关心的方式更改记录器,因此并没有真正的共享状态。这消除了反对单例模式的主要论点,并使其成为合理的选择,因为它们易于执行任务。

这是来自http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html的报价:

现在,有一种Singleton可以。那是所有可达对象都是不可变的单例。如果所有对象都是不可变的,则Singleton没有全局状态,因为一切都是恒定的。但是将这种单身人士变成易变的人是如此容易,这是很滑的坡度。因此,我也反对这些Singleton,不是因为它们不好,而是因为它们很容易变坏。(作为一个附带说明,Java枚举就是这些单例。只要您不将状态放入枚举中就可以,所以请不要这样做。)

另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录就是一个很好的例子。它加载了Singletons和全局状态。这是可以接受的(因为它不会伤害您),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。此处的信息以一种方式流动:从您的应用程序进入记录器。甚至认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,所以记录器是可以接受的。如果您想让测试断言某些东西正在被记录,那么您仍然应该注入记录器,但是一般来说,记录器即使处于满状态也不会有害。

Use a Metaclass

I would recommend Method #2, but you’re better off using a metaclass than a base class. Here is a sample implementation:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(object):
    __metaclass__ = Singleton

Or in Python3

class Logger(metaclass=Singleton):
    pass

If you want to run __init__ every time the class is called, add

        else:
            cls._instances[cls].__init__(*args, **kwargs)

to the if statement in Singleton.__call__.

A few words about metaclasses. A metaclass is the class of a class; that is, a class is an instance of its metaclass. You find the metaclass of an object in Python with type(obj). Normal new-style classes are of type type. Logger in the code above will be of type class 'your_module.Singleton', just as the (only) instance of Logger will be of type class 'your_module.Logger'. When you call logger with Logger(), Python first asks the metaclass of Logger, Singleton, what to do, allowing instance creation to be pre-empted. This process is the same as Python asking a class what to do by calling __getattr__ when you reference one of it’s attributes by doing myclass.attribute.

A metaclass essentially decides what the definition of a class means and how to implement that definition. See for example http://code.activestate.com/recipes/498149/, which essentially recreates C-style structs in Python using metaclasses. The thread What are some (concrete) use-cases for metaclasses? also provides some examples, they generally seem to be related to declarative programming, especially as used in ORMs.

In this situation, if you use your Method #2, and a subclass defines a __new__ method, it will be executed every time you call SubClassOfSingleton() — because it is responsible for calling the method that returns the stored instance. With a metaclass, it will only be called once, when the only instance is created. You want to customize what it means to call the class, which is decided by it’s type.

In general, it makes sense to use a metaclass to implement a singleton. A singleton is special because is created only once, and a metaclass is the way you customize the creation of a class. Using a metaclass gives you more control in case you need to customize the singleton class definitions in other ways.

Your singletons won’t need multiple inheritance (because the metaclass is not a base class), but for subclasses of the created class that use multiple inheritance, you need to make sure the singleton class is the first / leftmost one with a metaclass that redefines __call__ This is very unlikely to be an issue. The instance dict is not in the instance’s namespace so it won’t accidentally overwrite it.

You will also hear that the singleton pattern violates the “Single Responsibility Principle” — each class should do only one thing. That way you don’t have to worry about messing up one thing the code does if you need to change another, because they are separate and encapsulated. The metaclass implementation passes this test. The metaclass is responsible for enforcing the pattern and the created class and subclasses need not be aware that they are singletons. Method #1 fails this test, as you noted with “MyClass itself is a a function, not a class, so you cannot call class methods from it.”

Python 2 and 3 Compatible Version

Writing something that works in both Python2 and 3 requires using a slightly more complicated scheme. Since metaclasses are usually subclasses of type type, it’s possible to use one to dynamically create an intermediary base class at run time with it as its metaclass and then use that as the baseclass of the public Singleton base class. It’s harder to explain than to do, as illustrated next:

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

An ironic aspect of this approach is that it’s using subclassing to implement a metaclass. One possible advantage is that, unlike with a pure metaclass, isinstance(inst, Singleton) will return True.

Corrections

On another topic, you’ve probably already noticed this, but the base class implementation in your original post is wrong. _instances needs to be referenced on the class, you need to use super() or you’re recursing, and __new__ is actually a static method that you have to pass the class to, not a class method, as the actual class hasn’t been created yet when it is called. All of these things will be true for a metaclass implementation as well.

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

Decorator Returning A Class

I originally was writing a comment but it was too long, so I’ll add this here. Method #4 is better than the other decorator version, but it’s more code than needed for a singleton, and it’s not as clear what it does.

The main problems stem from the class being it’s own base class. First, isn’t it weird to have a class be a subclass of a nearly identical class with the same name that exists only in its __class__ attribute? This also means that you can’t define any methods that call the method of the same name on their base class with super() because they will recurse. This means your class can’t customize __new__, and can’t derive from any classes that need __init__ called on them.

When to use the singleton pattern

Your use case is one of the better examples of wanting to use a singleton. You say in one of the comments “To me logging has always seemed a natural candidate for Singletons.” You’re absolutely right.

When people say singletons are bad, the most common reason is they are implicit shared state. While with global variables and top-level module imports are explicit shared state, other objects that are passed around are generally instantiated. This is a good point, with two exceptions.

The first, and one that gets mentioned in various places, is when the singletons are constant. Use of global constants, especially enums, is widely accepted, and considered sane because no matter what, none of the users can mess them up for any other user. This is equally true for a constant singleton.

The second exception, which get mentioned less, is the opposite — when the singleton is only a data sink, not a data source (directly or indirectly). This is why loggers feel like a “natural” use for singletons. As the various users are not changing the loggers in ways other users will care about, there is not really shared state. This negates the primary argument against the singleton pattern, and makes them a reasonable choice because of their ease of use for the task.

Here is a quote from http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:

Now, there is one kind of Singleton which is OK. That is a singleton where all of the reachable objects are immutable. If all objects are immutable than Singleton has no global state, as everything is constant. But it is so easy to turn this kind of singleton into mutable one, it is very slippery slope. Therefore, I am against these Singletons too, not because they are bad, but because it is very easy for them to go bad. (As a side note Java enumeration are just these kind of singletons. As long as you don’t put state into your enumeration you are OK, so please don’t.)

The other kind of Singletons, which are semi-acceptable are those which don’t effect the execution of your code, They have no “side effects”. Logging is perfect example. It is loaded with Singletons and global state. It is acceptable (as in it will not hurt you) because your application does not behave any different whether or not a given logger is enabled. The information here flows one way: From your application into the logger. Even thought loggers are global state since no information flows from loggers into your application, loggers are acceptable. You should still inject your logger if you want your test to assert that something is getting logged, but in general Loggers are not harmful despite being full of state.


回答 1

class Foo(object):
     pass

some_global_variable = Foo()

模块仅导入一次,其他一切都考虑不周。不要使用单例并且不要使用全局。

class Foo(object):
     pass

some_global_variable = Foo()

Modules are imported only once, everything else is overthinking. Don’t use singletons and try not to use globals.


回答 2

使用模块。它仅导入一次。在其中定义一些全局变量-它们将是单例的“属性”。添加一些功能-单例的“方法”。

Use a module. It is imported only once. Define some global variables in it – they will be singleton’s ‘attributes’. Add some functions – the singleton’s ‘methods’.


回答 3

您可能永远不需要Python中的单例。只需在一个模块中定义所有数据和功能,便拥有事实上的单例。

如果您真的绝对必须要有一个单例类,那么我可以考虑:

class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

使用方法:

from mysingleton import my_singleton
my_singleton.foo()

其中mysingleton.py是定义My_Singleton的文件名。之所以起作用,是因为第一次导入文件后,Python不会重新执行代码。

You probably never need a singleton in Python. Just define all your data and functions in a module and you have a de-facto singleton.

If you really absolutely have to have a singleton class then I’d go with:

class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

To use:

from mysingleton import my_singleton
my_singleton.foo()

where mysingleton.py is your filename that My_Singleton is defined in. This works because after the first time a file is imported, Python doesn’t re-execute the code.


回答 4

这是您的一线客:

singleton = lambda c: c()

使用方法如下:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

您的对象会被实例化。这可能是您想要的,也可能不是。

Here’s a one-liner for you:

singleton = lambda c: c()

Here’s how you use it:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

Your object gets instantiated eagerly. This may or may not be what you want.


回答 5

看看Stack Overflow问题,是否有一种简单,优雅的方法在Python中定义单例?有几种解决方案。

我强烈建议观看Alex Martelli关于python设计模式的演讲:第1 部分第2部分。特别是在第1部分中,他讨论了单例/共享状态对象。

Check out Stack Overflow question Is there a simple, elegant way to define singletons in Python? with several solutions.

I’d strongly recommend to watch Alex Martelli’s talks on design patterns in python: part 1 and part 2. In particular, in part 1 he talks about singletons/shared state objects.


回答 6

这是我自己的单例实现。您所要做的就是装饰教室。要获得单例,则必须使用该Instance方法。这是一个例子:

   @Singleton
   class Foo:
       def __init__(self):
           print 'Foo created'

   f = Foo() # Error, this isn't how you get the instance of a singleton

   f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
   g = Foo.Instance() # Returns already created instance

   print f is g # True

这是代码:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

Here’s my own implementation of singletons. All you have to do is decorate the class; to get the singleton, you then have to use the Instance method. Here’s an example:

   @Singleton
   class Foo:
       def __init__(self):
           print 'Foo created'

   f = Foo() # Error, this isn't how you get the instance of a singleton

   f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
   g = Foo.Instance() # Returns already created instance

   print f is g # True

And here’s the code:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

回答 7

方法3看起来很整洁,但是如果您希望程序同时在Python 2Python 3中运行,那么它将无法正常工作。即使使用Python版本的测试来保护单独的变体也失败了,因为Python 3版本在Python 2中给出了语法错误。

感谢Mike Watkins:http : //mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/。如果要使程序在Python 2和Python 3中都能工作,则需要执行以下操作:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

我认为作业中的“对象”需要替换为“ BaseClass”,但是我还没有尝试过(我尝试了如图所示的代码)。

Method 3 seems to be very neat, but if you want your program to run in both Python 2 and Python 3, it doesn’t work. Even protecting the separate variants with tests for the Python version fails, because the Python 3 version gives a syntax error in Python 2.

Thanks to Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/. If you want the program to work in both Python 2 and Python 3, you need to do something like:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

I presume that ‘object’ in the assignment needs to be replaced with the ‘BaseClass’, but I haven’t tried that (I have tried code as illustrated).


回答 8

好吧,除了同意关于模块级全局的Pythonic通用建议外,如何做到这一点:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

输出为:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

Well, other than agreeing with the general Pythonic suggestion on having module-level global, how about this:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

Output is:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

回答 9

这个怎么样:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

将其用作应该为单例的类上的装饰器。像这样:

@singleton
class MySingleton:
    #....

这类似于singleton = lambda c: c()另一个答案中的装饰器。与其他解决方案一样,唯一的实例具有类(MySingleton)的名称。但是,使用此解决方案,您仍然可以通过执行从类“创建”实例(实际上是唯一的实例)MySingleton()。它还会阻止您这样做来创建其他实例type(MySingleton)()(这也会返回相同的实例)。

How about this:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

Use it as a decorator on a class that should be a singleton. Like this:

@singleton
class MySingleton:
    #....

This is similar to the singleton = lambda c: c() decorator in another answer. Like the other solution, the only instance has name of the class (MySingleton). However, with this solution you can still “create” instances (actually get the only instance) from the class, by doing MySingleton(). It also prevents you from creating additional instances by doing type(MySingleton)() (that also returns the same instance).


回答 10

我将我扔进戒指。这是一个简单的装饰器。

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

我认为它具有一些其他解决方案的好处:

  • 这是简洁明了的(在我看来; D)。
  • 它的作用被完全封装。您无需更改的实现的任何事情YourClass。这包括不需要为您的类使用元类(请注意,上面的元类在工厂中,而不是“真实”类)。
  • 它不依赖于Monkey修补任何东西。
  • 对调用方法透明:
    • 调用者仍然简单地import YourClass,它看起来像一个类(因为是),并且可以正常使用它。无需使调用者适应工厂功能。
    • 什么YourClass()实例仍是的一个真正的实例YourClass您实现的,没有任何形式的代理,所以没有副作用的几率从产生。
    • isinstance(instance, YourClass) 并且类似的操作仍然可以按预期进行(尽管此位确实需要abc,因此排除了Python <2.6)。

我确实有一个缺点:实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我已经很少使用了它,以至从未碰到过这种需求,但是通过在工厂上使用实现__getattr__()将所有属性访问权限委派给真实类的自定义元类,可以很容易地对其进行纠正。

我实际上发现的一个相关模式更有用(并不是说我经常需要这些东西)是“唯一”模式,其中使用相同的参数实例化该类会导致返回相同的实例。即“每个参数单”。上面的内容很好地适应了这一点,并且变得更加简洁:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

综上所述,我确实同意以下一般性建议:如果您认为自己需要这些东西之一,则可能应该停一会儿,问问自己是否确实需要。YAGNI 99%的时间。

I’ll toss mine into the ring. It’s a simple decorator.

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

Benefits I think it has over some of the other solutions:

  • It’s clear and concise (to my eye ;D).
  • Its action is completely encapsulated. You don’t need to change a single thing about the implementation of YourClass. This includes not needing to use a metaclass for your class (note that the metaclass above is on the factory, not the “real” class).
  • It doesn’t rely on monkey-patching anything.
  • It’s transparent to callers:
    • Callers still simply import YourClass, it looks like a class (because it is), and they use it normally. No need to adapt callers to a factory function.
    • What YourClass() instantiates is still a true instance of the YourClass you implemented, not a proxy of any kind, so no chance of side effects resulting from that.
    • isinstance(instance, YourClass) and similar operations still work as expected (though this bit does require abc so precludes Python <2.6).

One downside does occur to me: classmethods and staticmethods of the real class are not transparently callable via the factory class hiding it. I’ve used this rarely enough that I’ve never happen to run into that need, but it would be easily rectified by using a custom metaclass on the factory that implements __getattr__() to delegate all-ish attribute access to the real class.

A related pattern I’ve actually found more useful (not that I’m saying these kinds of things are required very often at all) is a “Unique” pattern where instantiating the class with the same arguments results in getting back the same instance. I.e. a “singleton per arguments”. The above adapts to this well and becomes even more concise:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

All that said, I do agree with the general advice that if you think you need one of these things, you really should probably stop for a moment and ask yourself if you really do. 99% of the time, YAGNI.


回答 11

基于Tolli的答案的代码。

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

说明:

  1. 创建新类,继承自给定的类cls
    cls例如,在有人想要的情况下,它不会进行修改singleton(list)

  2. 创建实例。覆盖之前__new__是如此简单。

  3. 现在,当我们轻松创建实例后,请__new__使用之前定义的方法进行覆盖。
  4. 该函数instance仅在调用者期望的时候返回,否则抛出TypeError
    当有人尝试从装饰类继承时,不满足该条件。

  5. 如果__new__()返回的实例cls,那么新实例的__init__()方法将被调用一样__init__(self[, ...]),这里的自我是新实例,其余参数都一样传递给__new__()

    instance已经被初始化,所以__init__功能被什么都不做的功能所取代。

看到它在线上工作

Code based on Tolli’s answer.

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

Explanation:

  1. Create new class, inheriting from given cls
    (it doesn’t modify cls in case someone wants for example singleton(list))

  2. Create instance. Before overriding __new__ it’s so easy.

  3. Now, when we have easily created instance, overrides __new__ using method defined moment ago.
  4. The function returns instance only when it’s what the caller expects, otherwise raises TypeError.
    The condition is not met when someone attempts to inherit from decorated class.

  5. If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to __new__().

    instance is already initialized, so function replaces __init__ with function doing nothing.

See it working online


回答 12

它与晶圆厂的答案有些相似,但并不完全相同。

单合同并不要求我们能够多次调用构造函数。作为一个单例应该仅创建一次,是否不应该将其视为仅创建一次?“欺骗”构造函数无疑会损害可读性。

所以我的建议是这样的:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

这不排除instance用户代码对构造函数或字段的使用:

if Elvis() is King.instance:

…如果您确定Elvis还没有创建,那就已经创建了King

但它鼓励用户the普遍使用该方法:

Elvis.the().leave(Building.the())

为了完成此操作,__delattr__()如果尝试删除instance,也可以重写以引发Exception ,并重写__del__()以引发Exception(除非我们知道程序正在结束…)

进一步的改进


感谢那些为评论和编辑提供帮助的人,我们欢迎其中的更多内容。当我使用Jython时,这应该更通用,并且是线程安全的。

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

注意事项:

  1. 如果您不从python2.x中的对象继承子类,则将获得一个老式的类,该类不使用 __new__
  2. 装饰时,__new__您必须使用@classmethod装饰,否则__new__它将是未绑定的实例方法
  3. 可以通过使用元类来改善这一点,因为这将使您能够创建the类级别的属性,并将其重命名为instance

It is slightly similar to the answer by fab but not exactly the same.

The singleton contract does not require that we be able to call the constructor multiple times. As a singleton should be created once and once only, shouldn’t it be seen to be created just once? “Spoofing” the constructor arguably impairs legibility.

So my suggestion is just this:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

This does not rule out the use of the constructor or the field instance by user code:

if Elvis() is King.instance:

… if you know for sure that Elvis has not yet been created, and that King has.

But it encourages users to use the the method universally:

Elvis.the().leave(Building.the())

To make this complete you could also override __delattr__() to raise an Exception if an attempt is made to delete instance, and override __del__() so that it raises an Exception (unless we know the program is ending…)

Further improvements


My thanks to those who have helped with comments and edits, of which more are welcome. While I use Jython, this should work more generally, and be thread-safe.

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

Points of note:

  1. If you don’t subclass from object in python2.x you will get an old-style class, which does not use __new__
  2. When decorating __new__ you must decorate with @classmethod or __new__ will be an unbound instance method
  3. This could possibly be improved by way of use of a metaclass, as this would allow you to make the a class-level property, possibly renaming it to instance

回答 13

一名班轮(我不为此感到自豪,但确实能胜任):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify

One liner (I am not proud, but it does the job):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify

回答 14

如果您不需要懒惰地初始化Singleton实例,则以下操作应该很容易且线程安全:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

这种方式A是在模块导入时初始化的单例。

If you don’t need lazy initialization of the instance of the Singleton, then the following should be easy and thread-safe:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

This way A is a singleton initialized at module import.


回答 15

  • 如果一个人想拥有多个相同类的实例,但是只有当args或kwargs不同时,才可以使用
  • 例如
    1. 如果您有类处理 serial通信,并且要创建一个实例,并且希望将串行端口作为参数发送,那么使用传统方法将无法正常工作
    2. 使用上述装饰器,如果args不同,则可以创建该类的多个实例。
    3. 对于相同的参数,装饰器将返回已经创建的相同实例。
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>
  • If one wants to have multiple number of instances of the same class, but only if the args or kwargs are different, one can use this
  • Ex.
    1. If you have a class handling serial communication, and to create an instance you want to send the serial port as an argument, then with traditional approach won’t work
    2. Using the above mentioned decorators, one can create multiple instances of the class if the args are different.
    3. For same args, the decorator will return the same instance which is already been created.
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

回答 16

也许我误解了单例模式,但是我的解决方案是这个简单而实用的(pythonic?)。该代码实现了两个目标

  1. 使实例可在Foo任何地方(全局)访问。
  2. 只能Foo存在一个实例。

这是代码。

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

输出量

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

Maybe I missunderstand the singleton pattern but my solution is this simple and pragmatic (pythonic?). This code fullfills two goals

  1. Make the instance of Foo accessiable everywhere (global).
  2. Only one instance of Foo can exist.

This is the code.

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

Output

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

回答 17

经过一段时间的努力,我最终想到了以下内容,以便从单独的模块中调用配置对象时,它只会被加载一次。元类允许将全局类实例存储在内置指令中,这在当前看来是存储适当程序全局的最简洁的方法。

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...

After struggling with this for some time I eventually came up with the following, so that the config object would only be loaded once, when called up from separate modules. The metaclass allows a global class instance to be stored in the builtins dict, which at present appears to be the neatest way of storing a proper program global.

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...

回答 18

我不记得在哪里找到该解决方案,但是从我的非Python专家的角度来看,它是最“优雅”的:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

我为什么喜欢这个?没有装饰器,没有元类,没有多重继承…,如果您决定不再希望它成为Singleton,只需删除该__new__方法。由于我是Python(和OOP的新手)的新手,所以我希望有人能使我明白为什么这是一种糟糕的方法?

I can’t remember where I found this solution, but I find it to be the most ‘elegant’ from my non-Python-expert point of view:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

Why do I like this? No decorators, no meta classes, no multiple inheritance…and if you decide you don’t want it to be a Singleton anymore, just delete the __new__ method. As I am new to Python (and OOP in general) I expect someone will set me straight about why this is a terrible approach?


回答 19

这是我实现单例的首选方式:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here

This is my preferred way of implementing singletons:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here

回答 20

这个答案可能不是您想要的。我想要一个单例,因为只有那个对象才具有其身份,以便进行比较。就我而言,它被用作前哨值。答案很简单,mything = object()根据python的性质制作任何对象,只有该对象才具有其标识。

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

This answer is likely not what you’re looking for. I wanted a singleton in the sense that only that object had its identity, for comparison to. In my case it was being used as a Sentinel Value. To which the answer is very simple, make any object mything = object() and by python’s nature, only that thing will have its identity.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

回答 21

该解决方案在模块级别上导致了一些命名空间污染(三个定义,而不仅仅是一个定义),但是我发现很容易遵循。

我希望能够编写这样的内容(延迟初始化),但不幸的是,类在其自身的定义中不可用。

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

由于这是不可能的,因此我们可以在其中分解初始化和静态实例

急切的初始化:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

延迟初始化:

急切的初始化:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None

This solution causes some namespace pollution at the module level (three definitions rather than just one), but I find it easy to follow.

I’d like to be able to write something like this (lazy initialization), but unfortunately classes are not available in the body of their own definitions.

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

Since that isn’t possible, we can break out the initialization and the static instance in

Eager Initialization:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

Lazy initialization:

Eager Initialization:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None

functools.wraps是做什么的?

问题:functools.wraps是做什么的?

对另一个问题的答案发表评论时,有人说他们不确定functools.wraps在做什么。所以,我问这个问题,以便在StackOverflow上有它的记录,以备将来参考:到底是functools.wraps做什么的?

In a comment on this answer to another question, someone said that they weren’t sure what functools.wraps was doing. So, I’m asking this question so that there will be a record of it on StackOverflow for future reference: what does functools.wraps do, exactly?


回答 0

使用装饰器时,您将一个功能替换为另一个功能。换句话说,如果您有一个装饰器

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

然后当你说

@logged
def f(x):
   """does some math"""
   return x + x * x

这和说的完全一样

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

并且您的函数f将替换为function with_logging。不幸的是,这意味着如果您然后说

print(f.__name__)

它会打印出来,with_logging因为那是新功能的名称。实际上,如果您查看的文档字符串f,则将为空,因为with_logging没有文档字符串,因此您编写的文档字符串将不再存在。另外,如果您查看该函数的pydoc结果,它不会被列为带有一个参数x;相反,它将被列为“接受” *args**kwargs因为那是with_logging的需要。

如果使用装饰器总是意味着丢失有关功能的信息,那将是一个严重的问题。这就是为什么functools.wraps。这需要一个装饰器中使用的函数,并添加了在函数名称,文档字符串,参数列表等上进行复制的功能。由于wraps它本身是一个装饰器,因此以下代码可以正确执行操作:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

When you use a decorator, you’re replacing one function with another. In other words, if you have a decorator

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

then when you say

@logged
def f(x):
   """does some math"""
   return x + x * x

it’s exactly the same as saying

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

and your function f is replaced with the function with_logging. Unfortunately, this means that if you then say

print(f.__name__)

it will print with_logging because that’s the name of your new function. In fact, if you look at the docstring for f, it will be blank because with_logging has no docstring, and so the docstring you wrote won’t be there anymore. Also, if you look at the pydoc result for that function, it won’t be listed as taking one argument x; instead it’ll be listed as taking *args and **kwargs because that’s what with_logging takes.

If using a decorator always meant losing this information about a function, it would be a serious problem. That’s why we have functools.wraps. This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And since wraps is itself a decorator, the following code does the correct thing:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

回答 1

我经常将类而不是函数用于装饰器。我遇到了一些麻烦,因为对象将不具有函数期望的所有相同属性。例如,对象将没有属性__name__。我有一个特定的问题,很难跟踪Django报告错误“对象没有属性’ __name__‘”的位置。不幸的是,对于类风格的装饰器,我认为@wrap不会胜任。相反,我创建了一个基本的装饰器类,如下所示:

class DecBase(object):
    func = None

    def __init__(self, func):
        self.__func = func

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

此类将所有属性调用代理到要修饰的函数。因此,您现在可以创建一个简单的装饰器来检查是否指定了2个参数,如下所示:

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)

I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won’t have all the same attributes that are expected of a function. For example, an object won’t have the attribute __name__. I had a specific issue with this that was pretty hard to trace where Django was reporting the error “object has no attribute ‘__name__‘”. Unfortunately, for class-style decorators, I don’t believe that @wrap will do the job. I have instead created a base decorator class like so:

class DecBase(object):
    func = None

    def __init__(self, func):
        self.__func = func

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

This class proxies all the attribute calls over to the function that is being decorated. So, you can now create a simple decorator that checks that 2 arguments are specified like so:

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)

回答 2

从python 3.5开始:

@functools.wraps(f)
def g():
    pass

是的别名g = functools.update_wrapper(g, f)。它确实完成了三件事:

  • 它复制__module____name____qualname____doc__,和__annotations__属性fg。此默认列表位于中WRAPPER_ASSIGNMENTS,您可以在 functools源代码中
  • 它使用中的所有元素更新__dict__of 。(看到gf.__dict__WRAPPER_UPDATES源代码)
  • 它在上设置了新__wrapped__=f属性g

结果是g显示的名称,文档字符串,模块名称和签名与相同f。唯一的问题是,关于签名,这实际上不是真的:inspect.signature默认情况下,只是遵循包装器链。您可以inspect.signature(g, follow_wrapped=False)按照文档中的说明进行检查。这会产生令人讨厌的后果:

  • 即使提供的参数无效,包装代码也将执行。
  • 包装器代码无法轻松地从接收到的* args,** kwargs中使用其名称访问参数。实际上,必须处理所有情况(位置,关键字,默认),因此要使用Signature.bind()

现在functools.wraps,装饰器之间有些混乱,因为开发装饰器的一个非常常见的用例是包装功能。但是两者都是完全独立的概念。如果您有兴趣了解它们之间的区别,则可以为这两种方法实现帮助程序库:decopatch可以轻松编写装饰器,而makefun可以提供保留签名的替代方法@wraps。注意,它makefun依赖于与著名decorator库相同的可靠技巧。

As of python 3.5+:

@functools.wraps(f)
def g():
    pass

Is an alias for g = functools.update_wrapper(g, f). It does exactly three things:

  • it copies the __module__, __name__, __qualname__, __doc__, and __annotations__ attributes of f on g. This default list is in WRAPPER_ASSIGNMENTS, you can see it in the functools source.
  • it updates the __dict__ of g with all elements from f.__dict__. (see WRAPPER_UPDATES in the source)
  • it sets a new __wrapped__=f attribute on g

The consequence is that g appears as having the same name, docstring, module name, and signature than f. The only problem is that concerning the signature this is not actually true: it is just that inspect.signature follows wrapper chains by default. You can check it by using inspect.signature(g, follow_wrapped=False) as explained in the doc. This has annoying consequences:

  • the wrapper code will execute even when the provided arguments are invalid.
  • the wrapper code can not easily access an argument using its name, from the received *args, **kwargs. Indeed one would have to handle all cases (positional, keyword, default) and therefore to use something like Signature.bind().

Now there is a bit of confusion between functools.wraps and decorators, because a very frequent use case for developing decorators is to wrap functions. But both are completely independent concepts. If you’re interested in understanding the difference, I implemented helper libraries for both: decopatch to write decorators easily, and makefun to provide a signature-preserving replacement for @wraps. Note that makefun relies on the same proven trick than the famous decorator library.


回答 3

这是关于包装的源代码:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

this is the source code about wraps:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

回答 4

  1. 先决条件:您必须知道如何使用装饰器,尤其是包装器。该评论对其进行了解释,或者此链接也对其进行了很好的解释。

  2. 每当我们使用For例如:@wraps后跟我们自己的包装函数。根据此链接中给出的详细信息,它说

functools.wraps是方便函数,用于在定义包装函数时调用update_wrapper()作为函数装饰器。

它等效于partial(update_wrapper,wraped = wrapped,assigned = assigned,updated = updated)。

因此,@ wraps装饰器实际上会调用functools.partial(func [,* args] [,** keywords])。

functools.partial()定义说

partial()用于部分函数应用程序,该函数“冻结”函数的参数和/或关键字的某些部分,从而生成具有简化签名的新对象。例如,partial()可用于创建行为类似于int()函数的可调用对象,其中基本参数默认为两个:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

这使我得出以下结论:@wraps调用了partial(),并将包装函数作为参数传递给它。最后,partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。

  1. Prerequisite: You must know how to use decorators and specially with wraps. This comment explains it a bit clear or this link also explains it pretty well.

  2. Whenever we use For eg: @wraps followed by our own wrapper function. As per the details given in this link , it says that

functools.wraps is convenience function for invoking update_wrapper() as a function decorator, when defining a wrapper function.

It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).

So @wraps decorator actually gives a call to functools.partial(func[,*args][, **keywords]).

The functools.partial() definition says that

The partial() is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature. For example, partial() can be used to create a callable that behaves like the int() function where the base argument defaults to two:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

Which brings me to the conclusion that, @wraps gives a call to partial() and it passes your wrapper function as a parameter to it. The partial() in the end returns the simplified version i.e the object of what’s inside the wrapper function and not the wrapper function itself.


回答 5

简而言之,functools.wraps只是一个常规函数。让我们考虑这个官方示例。借助源代码,我们可以看到有关实现和运行步骤的更多详细信息,如下所示:

  1. wraps(f)返回一个对象,例如O1。它是Partial类的对象
  2. 下一步是@ O1 …,这是python中的修饰符符号。它的意思是

wrapper = O1 .__ call __(wrapper)

检查的执行__call__,我们看到这一步,(左侧)后包装成为对象造成的self.func(* self.args,* ARGS,** newkeywords)检查创建O1__new__,我们知道self.func是功能update_wrapper。它使用参数* args(右侧包装器)作为其第一个参数。检查update_wrapper的最后一步,可以看到返回了右侧包装器,并根据需要修改了一些属性。

In short, functools.wraps is just a regular function. Let’s consider this official example. With the help of the source code, we can see more details about the implementation and the running steps as follows:

  1. wraps(f) returns an object, say O1. It is an object of the class Partial
  2. The next step is @O1… which is the decorator notation in python. It means

wrapper=O1.__call__(wrapper)

Checking the implementation of __call__, we see that after this step, (the left hand side )wrapper becomes the object resulted by self.func(*self.args, *args, **newkeywords) Checking the creation of O1 in __new__, we know self.func is the function update_wrapper. It uses the parameter *args, the right hand side wrapper, as its 1st parameter. Checking the last step of update_wrapper, one can see the right hand side wrapper is returned, with some of attributes modified as needed.


如何制作功能装饰器链?

问题:如何制作功能装饰器链?

如何在Python中制作两个装饰器,以完成以下工作?

@makebold
@makeitalic
def say():
   return "Hello"

…应返回:

"<b><i>Hello</i></b>"

我并不是想HTML在实际的应用程序中采用这种方式-只是想了解装饰器和装饰器链接的工作方式。

How can I make two decorators in Python that would do the following?

@makebold
@makeitalic
def say():
   return "Hello"

…which should return:

"<b><i>Hello</i></b>"

I’m not trying to make HTML this way in a real application – just trying to understand how decorators and decorator chaining works.


回答 0

查看文档,以了解装饰器如何工作。这是您要求的:

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

@makebold
@makeitalic
def log(s):
    return s

print hello()        # returns "<b><i>hello world</i></b>"
print hello.__name__ # with functools.wraps() this returns "hello"
print log('hello')   # returns "<b><i>hello</i></b>"

Check out the documentation to see how decorators work. Here is what you asked for:

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

@makebold
@makeitalic
def log(s):
    return s

print hello()        # returns "<b><i>hello world</i></b>"
print hello.__name__ # with functools.wraps() this returns "hello"
print log('hello')   # returns "<b><i>hello</i></b>"

回答 1

如果您不做详细解释,请参阅保罗·贝尔甘蒂诺(Paolo Bergantino)的回答

装饰基础

Python的函数是对象

要了解装饰器,您必须首先了解函数是Python中的对象。这具有重要的后果。让我们来看一个简单的例子:

def shout(word="yes"):
    return word.capitalize()+"!"

print(shout())
# outputs : 'Yes!'

# As an object, you can assign the function to a variable like any other object 
scream = shout

# Notice we don't use parentheses: we are not calling the function,
# we are putting the function "shout" into the variable "scream".
# It means you can then call "shout" from "scream":

print(scream())
# outputs : 'Yes!'

# More than that, it means you can remove the old name 'shout',
# and the function will still be accessible from 'scream'

del shout
try:
    print(shout())
except NameError as e:
    print(e)
    #outputs: "name 'shout' is not defined"

print(scream())
# outputs: 'Yes!'

请记住这一点。我们很快会回头再说。

Python函数的另一个有趣特性是可以在另一个函数中定义它们!

def talk():

    # You can define a function on the fly in "talk" ...
    def whisper(word="yes"):
        return word.lower()+"..."

    # ... and use it right away!
    print(whisper())

# You call "talk", that defines "whisper" EVERY TIME you call it, then
# "whisper" is called in "talk". 
talk()
# outputs: 
# "yes..."

# But "whisper" DOES NOT EXIST outside "talk":

try:
    print(whisper())
except NameError as e:
    print(e)
    #outputs : "name 'whisper' is not defined"*
    #Python's functions are objects

功能参考

好吧,还在吗?现在有趣的部分…

您已经看到函数是对象。因此,功能:

  • 可以分配给变量
  • 可以在另一个函数中定义

这意味着一个功能可以return另一个功能

def getTalk(kind="shout"):

    # We define functions on the fly
    def shout(word="yes"):
        return word.capitalize()+"!"

    def whisper(word="yes") :
        return word.lower()+"...";

    # Then we return one of them
    if kind == "shout":
        # We don't use "()", we are not calling the function,
        # we are returning the function object
        return shout  
    else:
        return whisper

# How do you use this strange beast?

# Get the function and assign it to a variable
talk = getTalk()      

# You can see that "talk" is here a function object:
print(talk)
#outputs : <function shout at 0xb7ea817c>

# The object is the one returned by the function:
print(talk())
#outputs : Yes!

# And you can even use it directly if you feel wild:
print(getTalk("whisper")())
#outputs : yes...

还有更多!

如果可以return使用函数,则可以将其中一个作为参数传递:

def doSomethingBefore(func): 
    print("I do something before then I call the function you gave me")
    print(func())

doSomethingBefore(scream)
#outputs: 
#I do something before then I call the function you gave me
#Yes!

好吧,您只需具备了解装饰器所需的一切。您会看到,装饰器是“包装器”,这意味着它们使您可以在装饰函数之前和之后执行代码,而无需修改函数本身。

手工装饰

您将如何手动进行操作:

# A decorator is a function that expects ANOTHER function as parameter
def my_shiny_new_decorator(a_function_to_decorate):

    # Inside, the decorator defines a function on the fly: the wrapper.
    # This function is going to be wrapped around the original function
    # so it can execute code before and after it.
    def the_wrapper_around_the_original_function():

        # Put here the code you want to be executed BEFORE the original function is called
        print("Before the function runs")

        # Call the function here (using parentheses)
        a_function_to_decorate()

        # Put here the code you want to be executed AFTER the original function is called
        print("After the function runs")

    # At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED.
    # We return the wrapper function we have just created.
    # The wrapper contains the function and the code to execute before and after. It’s ready to use!
    return the_wrapper_around_the_original_function

# Now imagine you create a function you don't want to ever touch again.
def a_stand_alone_function():
    print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function() 
#outputs: I am a stand alone function, don't you dare modify me

# Well, you can decorate it to extend its behavior.
# Just pass it to the decorator, it will wrap it dynamically in 
# any code you want and return you a new function ready to be used:

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

现在,您可能希望每次调用a_stand_alone_functiona_stand_alone_function_decorated都调用它。这很简单,只需a_stand_alone_function用以下方法返回的函数覆盖my_shiny_new_decorator

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

# That’s EXACTLY what decorators do!

装饰者神秘化

上一个使用装饰器语法的示例:

@my_shiny_new_decorator
def another_stand_alone_function():
    print("Leave me alone")

another_stand_alone_function()  
#outputs:  
#Before the function runs
#Leave me alone
#After the function runs

是的,仅此而已。@decorator只是以下方面的捷径:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

装饰器只是装饰器设计模式的pythonic变体。Python中嵌入了几种经典的设计模式来简化开发(例如迭代器)。

当然,您可以积累装饰器:

def bread(func):
    def wrapper():
        print("</''''''\>")
        func()
        print("<\______/>")
    return wrapper

def ingredients(func):
    def wrapper():
        print("#tomatoes#")
        func()
        print("~salad~")
    return wrapper

def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

使用Python装饰器语法:

@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

您设置装饰器重要事项的顺序:

@ingredients
@bread
def strange_sandwich(food="--ham--"):
    print(food)

strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~

现在:回答问题…

作为结论,您可以轻松地看到如何回答该问题:

# The decorator to make it bold
def makebold(fn):
    # The new function the decorator returns
    def wrapper():
        # Insertion of some code before and after
        return "<b>" + fn() + "</b>"
    return wrapper

# The decorator to make it italic
def makeitalic(fn):
    # The new function the decorator returns
    def wrapper():
        # Insertion of some code before and after
        return "<i>" + fn() + "</i>"
    return wrapper

@makebold
@makeitalic
def say():
    return "hello"

print(say())
#outputs: <b><i>hello</i></b>

# This is the exact equivalent to 
def say():
    return "hello"
say = makebold(makeitalic(say))

print(say())
#outputs: <b><i>hello</i></b>

现在,您可以放开心心,或者多动脑筋,看看装饰器的高级用法。


将装饰者提升到一个新的水平

将参数传递给装饰函数

# It’s not black magic, you just have to let the wrapper 
# pass the argument:

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print("I got args! Look: {0}, {1}".format(arg1, arg2))
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

# Since when you are calling the function returned by the decorator, you are
# calling the wrapper, passing arguments to the wrapper will let it pass them to 
# the decorated function

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print("My name is {0} {1}".format(first_name, last_name))

print_full_name("Peter", "Venkman")
# outputs:
#I got args! Look: Peter Venkman
#My name is Peter Venkman

装饰方式

关于Python的一件事是方法和函数实际上是相同的。唯一的区别是方法期望它们的第一个参数是对当前对象(self)的引用。

这意味着您可以以相同的方式为方法构建装饰器!只要记住要self考虑:

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # very friendly, decrease age even more :-)
        return method_to_decorate(self, lie)
    return wrapper


class Lucy(object):

    def __init__(self):
        self.age = 32

    @method_friendly_decorator
    def sayYourAge(self, lie):
        print("I am {0}, what did you think?".format(self.age + lie))

l = Lucy()
l.sayYourAge(-3)
#outputs: I am 26, what did you think?

如果要制作通用装饰器(无论参数如何,您都可以将其应用于任何函数或方法),则只需使用*args, **kwargs

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # The wrapper accepts any arguments
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print("Do I have args?:")
        print(args)
        print(kwargs)
        # Then you unpack the arguments, here *args, **kwargs
        # If you are not familiar with unpacking, check:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("Python is cool, no argument here.")

function_with_no_argument()
#outputs
#Do I have args?:
#()
#{}
#Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1,2,3)
#outputs
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3 

@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
    print("Do {0}, {1} and {2} like platypus? {3}".format(a, b, c, platypus))

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#outputs
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):

    def __init__(self):
        self.age = 31

    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # You can now add a default value
        print("I am {0}, what did you think?".format(self.age + lie))

m = Mary()
m.sayYourAge()
#outputs
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

将参数传递给装饰器

太好了,关于将参数传递给装饰器本身,您会说什么?

因为装饰器必须接受一个函数作为参数,所以这可能会有些扭曲。因此,您不能将装饰函数的参数直接传递给装饰器。

在寻求解决方案之前,让我们写一些提醒:

# Decorators are ORDINARY functions
def my_decorator(func):
    print("I am an ordinary function")
    def wrapper():
        print("I am function returned by the decorator")
        func()
    return wrapper

# Therefore, you can call it without any "@"

def lazy_function():
    print("zzzzzzzz")

decorated_function = my_decorator(lazy_function)
#outputs: I am an ordinary function

# It outputs "I am an ordinary function", because that’s just what you do:
# calling a function. Nothing magic.

@my_decorator
def lazy_function():
    print("zzzzzzzz")

#outputs: I am an ordinary function

完全一样 “ my_decorator”被调用。因此,当您使用时@my_decorator,您要告诉Python调用函数“由变量“ my_decorator” 标记”。

这个很重要!你给的标签可以直接指向decorator- 与否

让我们变得邪恶。☺

def decorator_maker():

    print("I make decorators! I am executed only once: "
          "when you make me create a decorator.")

    def my_decorator(func):

        print("I am a decorator! I am executed only when you decorate a function.")

        def wrapped():
            print("I am the wrapper around the decorated function. "
                  "I am called when you call the decorated function. "
                  "As the wrapper, I return the RESULT of the decorated function.")
            return func()

        print("As the decorator, I return the wrapped function.")

        return wrapped

    print("As a decorator maker, I return a decorator")
    return my_decorator

# Let’s create a decorator. It’s just a new function after all.
new_decorator = decorator_maker()       
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

# Then we decorate the function

def decorated_function():
    print("I am the decorated function.")

decorated_function = new_decorator(decorated_function)
#outputs:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function

# Let’s call the function:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

毫不奇怪。

让我们做完全一样的事情,但是跳过所有讨厌的中间变量:

def decorated_function():
    print("I am the decorated function.")
decorated_function = decorator_maker()(decorated_function)
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# Finally:
decorated_function()    
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

让我们把它变得更短

@decorator_maker()
def decorated_function():
    print("I am the decorated function.")
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

#Eventually: 
decorated_function()    
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

嘿,你看到了吗?我们使用了带有“ @”语法的函数调用!:-)

因此,回到带有参数的装饰器。如果我们可以使用函数即时生成装饰器,则可以将参数传递给该函数,对吗?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

    def my_decorator(func):
        # The ability to pass arguments here is a gift from closures.
        # If you are not comfortable with closures, you can assume it’s ok,
        # or read: /programming/13857/can-you-explain-closures-as-they-relate-to-python
        print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

        # Don't confuse decorator arguments and function arguments!
        def wrapped(function_arg1, function_arg2) :
            print("I am the wrapper around the decorated function.\n"
                  "I can access all the variables\n"
                  "\t- from the decorator: {0} {1}\n"
                  "\t- from the function call: {2} {3}\n"
                  "Then I can pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)

        return wrapped

    return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print("I am the decorated function and only knows about my arguments: {0}"
           " {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments("Rajesh", "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Sheldon 
#   - from the function call: Rajesh Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

它是:带参数的装饰器。可以将参数设置为变量:

c1 = "Penny"
c2 = "Leslie"

@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print("I am the decorated function and only knows about my arguments:"
           " {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Penny 
#   - from the function call: Leslie Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only know about my arguments: Leslie Howard

如您所见,可以使用此技巧像其他任何函数一样将参数传递给装饰器。您甚至可以*args, **kwargs根据需要使用。但是请记住,装饰器被调用一次。就在Python导入脚本时。之后,您将无法动态设置参数。当您执行“ import x”时,该函数已经被修饰,因此您无法进行任何更改。


练习:装饰装饰器

好的,作为奖励,我将向您提供一个片段,以使任何装饰器通常接受任何参数。毕竟,为了接受参数,我们使用了另一个函数来创建装饰器。

我们包装了装饰器。

我们最近看到的还有其他包装功能吗?

哦,是的,装饰品!

让我们玩得开心,为装饰者写一个装饰者:

def decorator_with_args(decorator_to_enhance):
    """ 
    This function is supposed to be used as a decorator.
    It must decorate an other function, that is intended to be used as a decorator.
    Take a cup of coffee.
    It will allow any decorator to accept an arbitrary number of arguments,
    saving you the headache to remember how to do that every time.
    """

    # We use the same trick we did to pass arguments
    def decorator_maker(*args, **kwargs):

        # We create on the fly a decorator that accepts only a function
        # but keeps the passed arguments from the maker.
        def decorator_wrapper(func):

            # We return the result of the original decorator, which, after all, 
            # IS JUST AN ORDINARY FUNCTION (which returns a function).
            # Only pitfall: the decorator must have this specific signature or it won't work:
            return decorator_to_enhance(func, *args, **kwargs)

        return decorator_wrapper

    return decorator_maker

可以如下使用:

# You create the function you will use as a decorator. And stick a decorator on it :-)
# Don't forget, the signature is "decorator(func, *args, **kwargs)"
@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print("Decorated with {0} {1}".format(args, kwargs))
        return func(function_arg1, function_arg2)
    return wrapper

# Then you decorate the functions you wish with your brand new decorated decorator.

@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print("Hello {0} {1}".format(function_arg1, function_arg2))

decorated_function("Universe and", "everything")
#outputs:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

# Whoooot!

我知道,上一次您有这种感觉时,是在听一个人说:“了解递归之前,您必须先了解递归”。但是现在,您是否对掌握这一点感到满意?


最佳做法:装饰

  • 装饰器是在Python 2.4中引入的,因此请确保您的代码将在> = 2.4上运行。
  • 装饰器使函数调用变慢。记住这一点。
  • 您不能取消装饰功能。(有一些技巧可以创建可以删除的装饰器,但是没有人使用它们。)因此,一旦装饰了函数,就对所有代码进行装饰。
  • 装饰器包装函数,这会使它们难以调试。(这在Python> = 2.5时会更好;请参见下文。)

functools模块是在Python 2.5中引入的。它包括功能functools.wraps(),该将修饰后的函数的名称,模块和文档字符串复制到其包装器中。

(有趣的事实:functools.wraps()是一个装饰!☺)

# For debugging, the stacktrace prints you the function __name__
def foo():
    print("foo")

print(foo.__name__)
#outputs: foo

# With a decorator, it gets messy    
def bar(func):
    def wrapper():
        print("bar")
        return func()
    return wrapper

@bar
def foo():
    print("foo")

print(foo.__name__)
#outputs: wrapper

# "functools" can help for that

import functools

def bar(func):
    # We say that "wrapper", is wrapping "func"
    # and the magic begins
    @functools.wraps(func)
    def wrapper():
        print("bar")
        return func()
    return wrapper

@bar
def foo():
    print("foo")

print(foo.__name__)
#outputs: foo

装饰器如何发挥作用?

现在有个大问题:我可以使用装饰器做什么?

看起来很酷而且功能强大,但是一个实际的例子将是很好的。好吧,这里有1000种可能性。经典用法是从外部库扩展功能行为(您不能对其进行修改),或者用于调试(您不希望对其进行修改,因为它是临时的)。

您可以使用它们以DRY的方式扩展多个功能,如下所示:

def benchmark(func):
    """
    A decorator that prints the time a function takes
    to execute.
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print("{0} {1}".format(func.__name__, time.clock()-t))
        return res
    return wrapper


def logging(func):
    """
    A decorator that logs the activity of the script.
    (it actually just prints it, but it could be logging!)
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print("{0} {1} {2}".format(func.__name__, args, kwargs))
        return res
    return wrapper


def counter(func):
    """
    A decorator that counts and prints the number of times a function has been executed
    """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print("{0} has been used: {1}x".format(func.__name__, wrapper.count))
        return res
    wrapper.count = 0
    return wrapper

@counter
@benchmark
@logging
def reverse_string(string):
    return str(reversed(string))

print(reverse_string("Able was I ere I saw Elba"))
print(reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!"))

#outputs:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x 
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

当然,装饰器的好处是,您几乎可以在几乎所有内容上使用它们而无需重写。干,我说:

@counter
@benchmark
@logging
def get_random_futurama_quote():
    from urllib import urlopen
    result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read()
    try:
        value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0]
        return value.strip()
    except:
        return "No, I'm ... doesn't!"


print(get_random_futurama_quote())
print(get_random_futurama_quote())

#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

Python本身提供了一些装饰:propertystaticmethod,等。

  • Django使用装饰器来管理缓存和查看权限。
  • 扭曲到伪造的内联异步函数调用。

这真的是一个大操场。

If you are not into long explanations, see Paolo Bergantino’s answer.

Decorator Basics

Python’s functions are objects

To understand decorators, you must first understand that functions are objects in Python. This has important consequences. Let’s see why with a simple example :

def shout(word="yes"):
    return word.capitalize()+"!"

print(shout())
# outputs : 'Yes!'

# As an object, you can assign the function to a variable like any other object 
scream = shout

# Notice we don't use parentheses: we are not calling the function,
# we are putting the function "shout" into the variable "scream".
# It means you can then call "shout" from "scream":

print(scream())
# outputs : 'Yes!'

# More than that, it means you can remove the old name 'shout',
# and the function will still be accessible from 'scream'

del shout
try:
    print(shout())
except NameError as e:
    print(e)
    #outputs: "name 'shout' is not defined"

print(scream())
# outputs: 'Yes!'

Keep this in mind. We’ll circle back to it shortly.

Another interesting property of Python functions is they can be defined inside another function!

def talk():

    # You can define a function on the fly in "talk" ...
    def whisper(word="yes"):
        return word.lower()+"..."

    # ... and use it right away!
    print(whisper())

# You call "talk", that defines "whisper" EVERY TIME you call it, then
# "whisper" is called in "talk". 
talk()
# outputs: 
# "yes..."

# But "whisper" DOES NOT EXIST outside "talk":

try:
    print(whisper())
except NameError as e:
    print(e)
    #outputs : "name 'whisper' is not defined"*
    #Python's functions are objects

Functions references

Okay, still here? Now the fun part…

You’ve seen that functions are objects. Therefore, functions:

  • can be assigned to a variable
  • can be defined in another function

That means that a function can return another function.

def getTalk(kind="shout"):

    # We define functions on the fly
    def shout(word="yes"):
        return word.capitalize()+"!"

    def whisper(word="yes") :
        return word.lower()+"...";

    # Then we return one of them
    if kind == "shout":
        # We don't use "()", we are not calling the function,
        # we are returning the function object
        return shout  
    else:
        return whisper

# How do you use this strange beast?

# Get the function and assign it to a variable
talk = getTalk()      

# You can see that "talk" is here a function object:
print(talk)
#outputs : <function shout at 0xb7ea817c>

# The object is the one returned by the function:
print(talk())
#outputs : Yes!

# And you can even use it directly if you feel wild:
print(getTalk("whisper")())
#outputs : yes...

There’s more!

If you can return a function, you can pass one as a parameter:

def doSomethingBefore(func): 
    print("I do something before then I call the function you gave me")
    print(func())

doSomethingBefore(scream)
#outputs: 
#I do something before then I call the function you gave me
#Yes!

Well, you just have everything needed to understand decorators. You see, decorators are “wrappers”, which means that they let you execute code before and after the function they decorate without modifying the function itself.

Handcrafted decorators

How you’d do it manually:

# A decorator is a function that expects ANOTHER function as parameter
def my_shiny_new_decorator(a_function_to_decorate):

    # Inside, the decorator defines a function on the fly: the wrapper.
    # This function is going to be wrapped around the original function
    # so it can execute code before and after it.
    def the_wrapper_around_the_original_function():

        # Put here the code you want to be executed BEFORE the original function is called
        print("Before the function runs")

        # Call the function here (using parentheses)
        a_function_to_decorate()

        # Put here the code you want to be executed AFTER the original function is called
        print("After the function runs")

    # At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED.
    # We return the wrapper function we have just created.
    # The wrapper contains the function and the code to execute before and after. It’s ready to use!
    return the_wrapper_around_the_original_function

# Now imagine you create a function you don't want to ever touch again.
def a_stand_alone_function():
    print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function() 
#outputs: I am a stand alone function, don't you dare modify me

# Well, you can decorate it to extend its behavior.
# Just pass it to the decorator, it will wrap it dynamically in 
# any code you want and return you a new function ready to be used:

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

Now, you probably want that every time you call a_stand_alone_function, a_stand_alone_function_decorated is called instead. That’s easy, just overwrite a_stand_alone_function with the function returned by my_shiny_new_decorator:

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

# That’s EXACTLY what decorators do!

Decorators demystified

The previous example, using the decorator syntax:

@my_shiny_new_decorator
def another_stand_alone_function():
    print("Leave me alone")

another_stand_alone_function()  
#outputs:  
#Before the function runs
#Leave me alone
#After the function runs

Yes, that’s all, it’s that simple. @decorator is just a shortcut to:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

Decorators are just a pythonic variant of the decorator design pattern. There are several classic design patterns embedded in Python to ease development (like iterators).

Of course, you can accumulate decorators:

def bread(func):
    def wrapper():
        print("</''''''\>")
        func()
        print("<\______/>")
    return wrapper

def ingredients(func):
    def wrapper():
        print("#tomatoes#")
        func()
        print("~salad~")
    return wrapper

def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

Using the Python decorator syntax:

@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

The order you set the decorators MATTERS:

@ingredients
@bread
def strange_sandwich(food="--ham--"):
    print(food)

strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~

Now: to answer the question…

As a conclusion, you can easily see how to answer the question:

# The decorator to make it bold
def makebold(fn):
    # The new function the decorator returns
    def wrapper():
        # Insertion of some code before and after
        return "<b>" + fn() + "</b>"
    return wrapper

# The decorator to make it italic
def makeitalic(fn):
    # The new function the decorator returns
    def wrapper():
        # Insertion of some code before and after
        return "<i>" + fn() + "</i>"
    return wrapper

@makebold
@makeitalic
def say():
    return "hello"

print(say())
#outputs: <b><i>hello</i></b>

# This is the exact equivalent to 
def say():
    return "hello"
say = makebold(makeitalic(say))

print(say())
#outputs: <b><i>hello</i></b>

You can now just leave happy, or burn your brain a little bit more and see advanced uses of decorators.


Taking decorators to the next level

Passing arguments to the decorated function

# It’s not black magic, you just have to let the wrapper 
# pass the argument:

def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print("I got args! Look: {0}, {1}".format(arg1, arg2))
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

# Since when you are calling the function returned by the decorator, you are
# calling the wrapper, passing arguments to the wrapper will let it pass them to 
# the decorated function

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print("My name is {0} {1}".format(first_name, last_name))

print_full_name("Peter", "Venkman")
# outputs:
#I got args! Look: Peter Venkman
#My name is Peter Venkman

Decorating methods

One nifty thing about Python is that methods and functions are really the same. The only difference is that methods expect that their first argument is a reference to the current object (self).

That means you can build a decorator for methods the same way! Just remember to take self into consideration:

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # very friendly, decrease age even more :-)
        return method_to_decorate(self, lie)
    return wrapper


class Lucy(object):

    def __init__(self):
        self.age = 32

    @method_friendly_decorator
    def sayYourAge(self, lie):
        print("I am {0}, what did you think?".format(self.age + lie))

l = Lucy()
l.sayYourAge(-3)
#outputs: I am 26, what did you think?

If you’re making general-purpose decorator–one you’ll apply to any function or method, no matter its arguments–then just use *args, **kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # The wrapper accepts any arguments
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print("Do I have args?:")
        print(args)
        print(kwargs)
        # Then you unpack the arguments, here *args, **kwargs
        # If you are not familiar with unpacking, check:
        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
        function_to_decorate(*args, **kwargs)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("Python is cool, no argument here.")

function_with_no_argument()
#outputs
#Do I have args?:
#()
#{}
#Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1,2,3)
#outputs
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3 

@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
    print("Do {0}, {1} and {2} like platypus? {3}".format(a, b, c, platypus))

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#outputs
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):

    def __init__(self):
        self.age = 31

    @a_decorator_passing_arbitrary_arguments
    def sayYourAge(self, lie=-3): # You can now add a default value
        print("I am {0}, what did you think?".format(self.age + lie))

m = Mary()
m.sayYourAge()
#outputs
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

Passing arguments to the decorator

Great, now what would you say about passing arguments to the decorator itself?

This can get somewhat twisted, since a decorator must accept a function as an argument. Therefore, you cannot pass the decorated function’s arguments directly to the decorator.

Before rushing to the solution, let’s write a little reminder:

# Decorators are ORDINARY functions
def my_decorator(func):
    print("I am an ordinary function")
    def wrapper():
        print("I am function returned by the decorator")
        func()
    return wrapper

# Therefore, you can call it without any "@"

def lazy_function():
    print("zzzzzzzz")

decorated_function = my_decorator(lazy_function)
#outputs: I am an ordinary function

# It outputs "I am an ordinary function", because that’s just what you do:
# calling a function. Nothing magic.

@my_decorator
def lazy_function():
    print("zzzzzzzz")

#outputs: I am an ordinary function

It’s exactly the same. “my_decorator” is called. So when you @my_decorator, you are telling Python to call the function ‘labelled by the variable “my_decorator“‘.

This is important! The label you give can point directly to the decorator—or not.

Let’s get evil. ☺

def decorator_maker():

    print("I make decorators! I am executed only once: "
          "when you make me create a decorator.")

    def my_decorator(func):

        print("I am a decorator! I am executed only when you decorate a function.")

        def wrapped():
            print("I am the wrapper around the decorated function. "
                  "I am called when you call the decorated function. "
                  "As the wrapper, I return the RESULT of the decorated function.")
            return func()

        print("As the decorator, I return the wrapped function.")

        return wrapped

    print("As a decorator maker, I return a decorator")
    return my_decorator

# Let’s create a decorator. It’s just a new function after all.
new_decorator = decorator_maker()       
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

# Then we decorate the function

def decorated_function():
    print("I am the decorated function.")

decorated_function = new_decorator(decorated_function)
#outputs:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function

# Let’s call the function:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

No surprise here.

Let’s do EXACTLY the same thing, but skip all the pesky intermediate variables:

def decorated_function():
    print("I am the decorated function.")
decorated_function = decorator_maker()(decorated_function)
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# Finally:
decorated_function()    
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Let’s make it even shorter:

@decorator_maker()
def decorated_function():
    print("I am the decorated function.")
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

#Eventually: 
decorated_function()    
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Hey, did you see that? We used a function call with the “@” syntax! :-)

So, back to decorators with arguments. If we can use functions to generate the decorator on the fly, we can pass arguments to that function, right?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

    def my_decorator(func):
        # The ability to pass arguments here is a gift from closures.
        # If you are not comfortable with closures, you can assume it’s ok,
        # or read: https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
        print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))

        # Don't confuse decorator arguments and function arguments!
        def wrapped(function_arg1, function_arg2) :
            print("I am the wrapper around the decorated function.\n"
                  "I can access all the variables\n"
                  "\t- from the decorator: {0} {1}\n"
                  "\t- from the function call: {2} {3}\n"
                  "Then I can pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)

        return wrapped

    return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print("I am the decorated function and only knows about my arguments: {0}"
           " {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments("Rajesh", "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Sheldon 
#   - from the function call: Rajesh Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

Here it is: a decorator with arguments. Arguments can be set as variable:

c1 = "Penny"
c2 = "Leslie"

@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print("I am the decorated function and only knows about my arguments:"
           " {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function. 
#I can access all the variables 
#   - from the decorator: Leonard Penny 
#   - from the function call: Leslie Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only know about my arguments: Leslie Howard

As you can see, you can pass arguments to the decorator like any function using this trick. You can even use *args, **kwargs if you wish. But remember decorators are called only once. Just when Python imports the script. You can’t dynamically set the arguments afterwards. When you do “import x”, the function is already decorated, so you can’t change anything.


Let’s practice: decorating a decorator

Okay, as a bonus, I’ll give you a snippet to make any decorator accept generically any argument. After all, in order to accept arguments, we created our decorator using another function.

We wrapped the decorator.

Anything else we saw recently that wrapped function?

Oh yes, decorators!

Let’s have some fun and write a decorator for the decorators:

def decorator_with_args(decorator_to_enhance):
    """ 
    This function is supposed to be used as a decorator.
    It must decorate an other function, that is intended to be used as a decorator.
    Take a cup of coffee.
    It will allow any decorator to accept an arbitrary number of arguments,
    saving you the headache to remember how to do that every time.
    """

    # We use the same trick we did to pass arguments
    def decorator_maker(*args, **kwargs):

        # We create on the fly a decorator that accepts only a function
        # but keeps the passed arguments from the maker.
        def decorator_wrapper(func):

            # We return the result of the original decorator, which, after all, 
            # IS JUST AN ORDINARY FUNCTION (which returns a function).
            # Only pitfall: the decorator must have this specific signature or it won't work:
            return decorator_to_enhance(func, *args, **kwargs)

        return decorator_wrapper

    return decorator_maker

It can be used as follows:

# You create the function you will use as a decorator. And stick a decorator on it :-)
# Don't forget, the signature is "decorator(func, *args, **kwargs)"
@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print("Decorated with {0} {1}".format(args, kwargs))
        return func(function_arg1, function_arg2)
    return wrapper

# Then you decorate the functions you wish with your brand new decorated decorator.

@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
    print("Hello {0} {1}".format(function_arg1, function_arg2))

decorated_function("Universe and", "everything")
#outputs:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

# Whoooot!

I know, the last time you had this feeling, it was after listening a guy saying: “before understanding recursion, you must first understand recursion”. But now, don’t you feel good about mastering this?


Best practices: decorators

  • Decorators were introduced in Python 2.4, so be sure your code will be run on >= 2.4.
  • Decorators slow down the function call. Keep that in mind.
  • You cannot un-decorate a function. (There are hacks to create decorators that can be removed, but nobody uses them.) So once a function is decorated, it’s decorated for all the code.
  • Decorators wrap functions, which can make them hard to debug. (This gets better from Python >= 2.5; see below.)

The functools module was introduced in Python 2.5. It includes the function functools.wraps(), which copies the name, module, and docstring of the decorated function to its wrapper.

(Fun fact: functools.wraps() is a decorator! ☺)

# For debugging, the stacktrace prints you the function __name__
def foo():
    print("foo")

print(foo.__name__)
#outputs: foo

# With a decorator, it gets messy    
def bar(func):
    def wrapper():
        print("bar")
        return func()
    return wrapper

@bar
def foo():
    print("foo")

print(foo.__name__)
#outputs: wrapper

# "functools" can help for that

import functools

def bar(func):
    # We say that "wrapper", is wrapping "func"
    # and the magic begins
    @functools.wraps(func)
    def wrapper():
        print("bar")
        return func()
    return wrapper

@bar
def foo():
    print("foo")

print(foo.__name__)
#outputs: foo

How can the decorators be useful?

Now the big question: What can I use decorators for?

Seem cool and powerful, but a practical example would be great. Well, there are 1000 possibilities. Classic uses are extending a function behavior from an external lib (you can’t modify it), or for debugging (you don’t want to modify it because it’s temporary).

You can use them to extend several functions in a DRY’s way, like so:

def benchmark(func):
    """
    A decorator that prints the time a function takes
    to execute.
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print("{0} {1}".format(func.__name__, time.clock()-t))
        return res
    return wrapper


def logging(func):
    """
    A decorator that logs the activity of the script.
    (it actually just prints it, but it could be logging!)
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print("{0} {1} {2}".format(func.__name__, args, kwargs))
        return res
    return wrapper


def counter(func):
    """
    A decorator that counts and prints the number of times a function has been executed
    """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print("{0} has been used: {1}x".format(func.__name__, wrapper.count))
        return res
    wrapper.count = 0
    return wrapper

@counter
@benchmark
@logging
def reverse_string(string):
    return str(reversed(string))

print(reverse_string("Able was I ere I saw Elba"))
print(reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!"))

#outputs:
#reverse_string ('Able was I ere I saw Elba',) {}
#wrapper 0.0
#wrapper has been used: 1x 
#ablE was I ere I saw elbA
#reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

Of course the good thing with decorators is that you can use them right away on almost anything without rewriting. DRY, I said:

@counter
@benchmark
@logging
def get_random_futurama_quote():
    from urllib import urlopen
    result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read()
    try:
        value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0]
        return value.strip()
    except:
        return "No, I'm ... doesn't!"


print(get_random_futurama_quote())
print(get_random_futurama_quote())

#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

Python itself provides several decorators: property, staticmethod, etc.

  • Django uses decorators to manage caching and view permissions.
  • Twisted to fake inlining asynchronous functions calls.

This really is a large playground.


回答 2

另外,您可以编写一个工厂函数,该函数返回一个装饰器,该装饰器将装饰后的函数的返回值包装在传递给工厂函数的标记中。例如:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator():
            return '<%(tag)s>%(rv)s</%(tag)s>' % (
                {'tag': tag, 'rv': func()})
        return decorator
    return factory

这使您可以编写:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
    return 'hello'

要么

makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')

@makebold
@makeitalic
def say():
    return 'hello'

就个人而言,我会以不同的方式编写装饰器:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator(val):
            return func('<%(tag)s>%(val)s</%(tag)s>' %
                        {'tag': tag, 'val': val})
        return decorator
    return factory

这将生成:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
    return val
say('hello')

不要忘记装饰器语法是其速记形式的构造:

say = wrap_in_tag('b')(wrap_in_tag('i')(say)))

Alternatively, you could write a factory function which return a decorator which wraps the return value of the decorated function in a tag passed to the factory function. For example:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator():
            return '<%(tag)s>%(rv)s</%(tag)s>' % (
                {'tag': tag, 'rv': func()})
        return decorator
    return factory

This enables you to write:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
    return 'hello'

or

makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')

@makebold
@makeitalic
def say():
    return 'hello'

Personally I would have written the decorator somewhat differently:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator(val):
            return func('<%(tag)s>%(val)s</%(tag)s>' %
                        {'tag': tag, 'val': val})
        return decorator
    return factory

which would yield:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
    return val
say('hello')

Don’t forget the construction for which decorator syntax is a shorthand:

say = wrap_in_tag('b')(wrap_in_tag('i')(say)))

回答 3

看来其他人已经告诉您如何解决问题。希望这可以帮助您了解什么是装饰器。

装饰器只是语法糖。

这个

@decorator
def func():
    ...

扩展到

def func():
    ...
func = decorator(func)

It looks like the other people have already told you how to solve the problem. I hope this will help you understand what decorators are.

Decorators are just syntactical sugar.

This

@decorator
def func():
    ...

expands to

def func():
    ...
func = decorator(func)

回答 4

当然,您也可以从装饰器函数返回lambda:

def makebold(f): 
    return lambda: "<b>" + f() + "</b>"
def makeitalic(f): 
    return lambda: "<i>" + f() + "</i>"

@makebold
@makeitalic
def say():
    return "Hello"

print say()

And of course you can return lambdas as well from a decorator function:

def makebold(f): 
    return lambda: "<b>" + f() + "</b>"
def makeitalic(f): 
    return lambda: "<i>" + f() + "</i>"

@makebold
@makeitalic
def say():
    return "Hello"

print say()

回答 5

Python装饰器为其他功能添加了额外的功能

斜体装饰器可能像

def makeitalic(fn):
    def newFunc():
        return "<i>" + fn() + "</i>"
    return newFunc

请注意,函数是在函数内部定义的。它的主要作用是用新定义的功能替换功能。例如我有这个课

class foo:
    def bar(self):
        print "hi"
    def foobar(self):
        print "hi again"

现在说,我希望两个函数在完成之前和之后都打印“ —”。我可以在每个打印语句之前和之后添加打印“-”。但是因为我不喜欢重复自己,所以我会做一个装饰工

def addDashes(fn): # notice it takes a function as an argument
    def newFunction(self): # define a new function
        print "---"
        fn(self) # call the original function
        print "---"
    return newFunction
    # Return the newly defined function - it will "replace" the original

所以现在我可以将类更改为

class foo:
    @addDashes
    def bar(self):
        print "hi"

    @addDashes
    def foobar(self):
        print "hi again"

有关装饰器的更多信息,请 访问http://www.ibm.com/developerworks/linux/library/l-cpdecor.html。

Python decorators add extra functionality to another function

An italics decorator could be like

def makeitalic(fn):
    def newFunc():
        return "<i>" + fn() + "</i>"
    return newFunc

Note that a function is defined inside a function. What it basically does is replace a function with the newly defined one. For example, I have this class

class foo:
    def bar(self):
        print "hi"
    def foobar(self):
        print "hi again"

Now say, I want both functions to print “—” after and before they are done. I could add a print “—” before and after each print statement. But because I don’t like repeating myself, I will make a decorator

def addDashes(fn): # notice it takes a function as an argument
    def newFunction(self): # define a new function
        print "---"
        fn(self) # call the original function
        print "---"
    return newFunction
    # Return the newly defined function - it will "replace" the original

So now I can change my class to

class foo:
    @addDashes
    def bar(self):
        print "hi"

    @addDashes
    def foobar(self):
        print "hi again"

For more on decorators, check http://www.ibm.com/developerworks/linux/library/l-cpdecor.html


回答 6

可以制作两个单独的装饰器,按您的意愿进行操作,如下所示。请注意*args, **kwargs,在wrapped()函数的声明中使用,该声明支持具有多个参数的修饰函数(对于示例say()函数而言,这并不是必需的,但出于一般性考虑而包括在内)。

出于类似的原因,functools.wraps装饰器用于将包装功能的元属性更改为要装饰的功能的元属性。这使错误消息和嵌入式功能文档(func.__doc__)成为修饰后的函数的错误消息,而不是wrapped()

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

细化

如您所见,这两个装饰器中有很多重复的代码。鉴于这种相似性,您最好改成一个实际上是装饰器工厂的通用泛型,换句话说,就是制造其他装饰器的装饰器功能。这样,代码重复将更少,并允许遵循DRY原则。

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

为了使代码更具可读性,可以为工厂生成的装饰器分配一个更具描述性的名称:

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

甚至像这样组合它们:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

效率

尽管上面的示例可以完成所有工作,但是当同时应用多个装饰器时,生成的代码会以大量外部函数调用的形式涉及大量开销。这可能无关紧要,具体取决于确切的用法(例如,可能受I / O限制)。

如果装饰函数的速度很重要,则可以通过编写稍有不同的装饰器工厂函数(可一次添加所有标记)来将开销保持在一个额外的函数调用中,因此它可以生成避免发生附加函数调用的代码通过为每个标签使用单独的装饰器。

这需要装饰器本身中的更多代码,但这仅在将其应用于函数定义时才运行,而不是在调用它们本身时才运行。当通过使用lambda如前所述的函数创建更易读的名称时,这也适用。样品:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

You could make two separate decorators that do what you want as illustrated directly below. Note the use of *args, **kwargs in the declaration of the wrapped() function which supports the decorated function having multiple arguments (which isn’t really necessary for the example say() function, but is included for generality).

For similar reasons, the functools.wraps decorator is used to change the meta attributes of the wrapped function to be those of the one being decorated. This makes error messages and embedded function documentation (func.__doc__) be those of the decorated function instead of wrapped()‘s.

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

Refinements

As you can see there’s a lot of duplicate code in these two decorators. Given this similarity it would be better for you to instead make a generic one that was actually a decorator factory—in other words, a decorator function that makes other decorators. That way there would be less code repetition—and allow the DRY principle to be followed.

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

To make the code more readable, you can assign a more descriptive name to the factory-generated decorators:

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

or even combine them like this:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

Efficiency

While the above examples do all work, the code generated involves a fair amount of overhead in the form of extraneous function calls when multiple decorators are applied at once. This may not matter, depending the exact usage (which might be I/O-bound, for instance).

If speed of the decorated function is important, the overhead can be kept to a single extra function call by writing a slightly different decorator factory-function which implements adding all the tags at once, so it can generate code that avoids the addtional function calls incurred by using separate decorators for each tag.

This requires more code in the decorator itself, but this only runs when it’s being applied to function definitions, not later when they themselves are called. This also applies when creating more readable names by using lambda functions as previously illustrated. Sample:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

回答 7

做同样事情的另一种方式:

class bol(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<b>{}</b>".format(self.f())

class ita(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<i>{}</i>".format(self.f())

@bol
@ita
def sayhi():
  return 'hi'

或者,更灵活:

class sty(object):
  def __init__(self, tag):
    self.tag = tag
  def __call__(self, f):
    def newf():
      return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
    return newf

@sty('b')
@sty('i')
def sayhi():
  return 'hi'

Another way of doing the same thing:

class bol(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<b>{}</b>".format(self.f())

class ita(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<i>{}</i>".format(self.f())

@bol
@ita
def sayhi():
  return 'hi'

Or, more flexibly:

class sty(object):
  def __init__(self, tag):
    self.tag = tag
  def __call__(self, f):
    def newf():
      return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
    return newf

@sty('b')
@sty('i')
def sayhi():
  return 'hi'

回答 8

如何在Python中制作两个装饰器,以完成以下工作?

调用时需要以下函数:

@makebold
@makeitalic
def say():
    return "Hello"

回来:

<b><i>Hello</i></b>

简单的解决方案

为了最简单地执行此操作,请使装饰器返回关闭函数(闭包)的lambda(匿名函数)并调用它:

def makeitalic(fn):
    return lambda: '<i>' + fn() + '</i>'

def makebold(fn):
    return lambda: '<b>' + fn() + '</b>'

现在根据需要使用它们:

@makebold
@makeitalic
def say():
    return 'Hello'

现在:

>>> say()
'<b><i>Hello</i></b>'

简单解决方案的问题

但是我们似乎几乎失去了原来的功能。

>>> say
<function <lambda> at 0x4ACFA070>

为了找到它,我们需要深入研究每个lambda的闭包,其中一个埋藏在另一个中:

>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>

因此,如果我们在此函数上放上文档,或者希望能够修饰带有多个参数的函数,或者我们只想知道在调试会话中要查看的函数,则需要对我们做更多的事情包装纸。

功能齐全的解决方案-克服了大多数此类问题

我们在标准库中提供wrapsfunctools模块中的装饰器!

from functools import wraps

def makeitalic(fn):
    # must assign/update attributes from wrapped function to wrapper
    # __module__, __name__, __doc__, and __dict__ by default
    @wraps(fn) # explicitly give function whose attributes it is applying
    def wrapped(*args, **kwargs):
        return '<i>' + fn(*args, **kwargs) + '</i>'
    return wrapped

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return '<b>' + fn(*args, **kwargs) + '</b>'
    return wrapped

不幸的是,仍然有一些样板,但这大约很简单。

在Python 3中,默认情况下也会获取__qualname____annotations__分配。

所以现在:

@makebold
@makeitalic
def say():
    """This function returns a bolded, italicized 'hello'"""
    return 'Hello'

现在:

>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:

say(*args, **kwargs)
    This function returns a bolded, italicized 'hello'

结论

因此,我们看到,wraps使包装函数几乎可以执行所有操作,只是确切告诉我们该函数将其用作参数。

还有其他模块可以尝试解决该问题,但是标准库中尚未提供该解决方案。

How can I make two decorators in Python that would do the following?

You want the following function, when called:

@makebold
@makeitalic
def say():
    return "Hello"

To return:

<b><i>Hello</i></b>

Simple solution

To most simply do this, make decorators that return lambdas (anonymous functions) that close over the function (closures) and call it:

def makeitalic(fn):
    return lambda: '<i>' + fn() + '</i>'

def makebold(fn):
    return lambda: '<b>' + fn() + '</b>'

Now use them as desired:

@makebold
@makeitalic
def say():
    return 'Hello'

and now:

>>> say()
'<b><i>Hello</i></b>'

Problems with the simple solution

But we seem to have nearly lost the original function.

>>> say
<function <lambda> at 0x4ACFA070>

To find it, we’d need to dig into the closure of each lambda, one of which is buried in the other:

>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>

So if we put documentation on this function, or wanted to be able to decorate functions that take more than one argument, or we just wanted to know what function we were looking at in a debugging session, we need to do a bit more with our wrapper.

Full featured solution – overcoming most of these problems

We have the decorator wraps from the functools module in the standard library!

from functools import wraps

def makeitalic(fn):
    # must assign/update attributes from wrapped function to wrapper
    # __module__, __name__, __doc__, and __dict__ by default
    @wraps(fn) # explicitly give function whose attributes it is applying
    def wrapped(*args, **kwargs):
        return '<i>' + fn(*args, **kwargs) + '</i>'
    return wrapped

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return '<b>' + fn(*args, **kwargs) + '</b>'
    return wrapped

It is unfortunate that there’s still some boilerplate, but this is about as simple as we can make it.

In Python 3, you also get __qualname__ and __annotations__ assigned by default.

So now:

@makebold
@makeitalic
def say():
    """This function returns a bolded, italicized 'hello'"""
    return 'Hello'

And now:

>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:

say(*args, **kwargs)
    This function returns a bolded, italicized 'hello'

Conclusion

So we see that wraps makes the wrapping function do almost everything except tell us exactly what the function takes as arguments.

There are other modules that may attempt to tackle the problem, but the solution is not yet in the standard library.


回答 9

以简单的方式说明装饰器:

带有:

@decor1
@decor2
def func(*args, **kwargs):
    pass

什么时候:

func(*args, **kwargs)

您确实做到了:

decor1(decor2(func))(*args, **kwargs)

To explain the decorator in a simple way:

With:

@decor1
@decor2
def func(*args, **kwargs):
    pass

When do:

func(*args, **kwargs)

You really do:

decor1(decor2(func))(*args, **kwargs)

回答 10

装饰器采用函数定义并创建一个新函数,该函数执行该函数并转换结果。

@deco
def do():
    ...

等效于:

do = deco(do)

例:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

这个

@deco
def do(number):
    return chr(number)  # number to letter

相当于这个

def do2(number):
    return chr(number)

do2 = deco(do2)

65 <=>’a’

print(do(65))
print(do2(65))
>>> B
>>> B

要了解装饰器,必须注意,装饰器创建了一个新的函数do,该函数在内部执行函数并转换结果。

A decorator takes the function definition and creates a new function that executes this function and transforms the result.

@deco
def do():
    ...

is equivalent to:

do = deco(do)

Example:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

This

@deco
def do(number):
    return chr(number)  # number to letter

is equivalent to this

def do2(number):
    return chr(number)

do2 = deco(do2)

65 <=> ‘a’

print(do(65))
print(do2(65))
>>> B
>>> B

To understand the decorator, it is important to notice, that decorator created a new function do which is inner that executes function and transforms the result.


回答 11

#decorator.py
def makeHtmlTag(tag, *args, **kwds):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwds["css_class"]) \
                                 if "css_class" in kwds else ""
        def wrapped(*args, **kwds):
            return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
        return wrapped
    # return decorator dont call it
    return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"

print hello()

您也可以在Class中编写装饰器

#class.py
class makeHtmlTagClass(object):
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class != "" else ""

    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped

@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)

print hello("Your name")
#decorator.py
def makeHtmlTag(tag, *args, **kwds):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwds["css_class"]) \
                                 if "css_class" in kwds else ""
        def wrapped(*args, **kwds):
            return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
        return wrapped
    # return decorator dont call it
    return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"

print hello()

You can also write decorator in Class

#class.py
class makeHtmlTagClass(object):
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class != "" else ""

    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped

@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)

print hello("Your name")

回答 12

这个答案早就得到了回答,但是我想我应该分享我的Decorator类,它使编写新的装饰器变得轻松而紧凑。

from abc import ABCMeta, abstractclassmethod

class Decorator(metaclass=ABCMeta):
    """ Acts as a base class for all decorators """

    def __init__(self):
        self.method = None

    def __call__(self, method):
        self.method = method
        return self.call

    @abstractclassmethod
    def call(self, *args, **kwargs):
        return self.method(*args, **kwargs)

我认为,这可以使修饰器的行为非常清晰,但是也可以很简洁地定义新的修饰器。对于上面列出的示例,您可以将其解决为:

class MakeBold(Decorator):
    def call():
        return "<b>" + self.method() + "</b>"

class MakeItalic(Decorator):
    def call():
        return "<i>" + self.method() + "</i>"

@MakeBold()
@MakeItalic()
def say():
   return "Hello"

您还可以使用它来执行更复杂的任务,例如装饰器,该装饰器自动使函数递归地应用于迭代器中的所有参数:

class ApplyRecursive(Decorator):
    def __init__(self, *types):
        super().__init__()
        if not len(types):
            types = (dict, list, tuple, set)
        self._types = types

    def call(self, arg):
        if dict in self._types and isinstance(arg, dict):
            return {key: self.call(value) for key, value in arg.items()}

        if set in self._types and isinstance(arg, set):
            return set(self.call(value) for value in arg)

        if tuple in self._types and isinstance(arg, tuple):
            return tuple(self.call(value) for value in arg)

        if list in self._types and isinstance(arg, list):
            return list(self.call(value) for value in arg)

        return self.method(arg)


@ApplyRecursive(tuple, set, dict)
def double(arg):
    return 2*arg

print(double(1))
print(double({'a': 1, 'b': 2}))
print(double({1, 2, 3}))
print(double((1, 2, 3, 4)))
print(double([1, 2, 3, 4, 5]))

哪些打印:

2
{'a': 2, 'b': 4}
{2, 4, 6}
(2, 4, 6, 8)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

请注意,此示例未list在装饰器的实例中包括类型,因此在最终的print语句中,方法将应用于列表本身,而不是列表的元素。

This answer has long been answered, but I thought I would share my Decorator class which makes writing new decorators easy and compact.

from abc import ABCMeta, abstractclassmethod

class Decorator(metaclass=ABCMeta):
    """ Acts as a base class for all decorators """

    def __init__(self):
        self.method = None

    def __call__(self, method):
        self.method = method
        return self.call

    @abstractclassmethod
    def call(self, *args, **kwargs):
        return self.method(*args, **kwargs)

For one I think this makes the behavior of decorators very clear, but it also makes it easy to define new decorators very concisely. For the example listed above, you could then solve it as:

class MakeBold(Decorator):
    def call():
        return "<b>" + self.method() + "</b>"

class MakeItalic(Decorator):
    def call():
        return "<i>" + self.method() + "</i>"

@MakeBold()
@MakeItalic()
def say():
   return "Hello"

You could also use it to do more complex tasks, like for instance a decorator which automatically makes the function get applied recursively to all arguments in an iterator:

class ApplyRecursive(Decorator):
    def __init__(self, *types):
        super().__init__()
        if not len(types):
            types = (dict, list, tuple, set)
        self._types = types

    def call(self, arg):
        if dict in self._types and isinstance(arg, dict):
            return {key: self.call(value) for key, value in arg.items()}

        if set in self._types and isinstance(arg, set):
            return set(self.call(value) for value in arg)

        if tuple in self._types and isinstance(arg, tuple):
            return tuple(self.call(value) for value in arg)

        if list in self._types and isinstance(arg, list):
            return list(self.call(value) for value in arg)

        return self.method(arg)


@ApplyRecursive(tuple, set, dict)
def double(arg):
    return 2*arg

print(double(1))
print(double({'a': 1, 'b': 2}))
print(double({1, 2, 3}))
print(double((1, 2, 3, 4)))
print(double([1, 2, 3, 4, 5]))

Which prints:

2
{'a': 2, 'b': 4}
{2, 4, 6}
(2, 4, 6, 8)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

Notice that this example didn’t include the list type in the instantiation of the decorator, so in the final print statement the method gets applied to the list itself, not the elements of the list.


回答 13

这是链接装饰器的简单示例。注意最后一行-它显示了幕后的内容。

############################################################
#
#    decorators
#
############################################################

def bold(fn):
    def decorate():
        # surround with bold tags before calling original function
        return "<b>" + fn() + "</b>"
    return decorate


def uk(fn):
    def decorate():
        # swap month and day
        fields = fn().split('/')
        date = fields[1] + "/" + fields[0] + "/" + fields[2]
        return date
    return decorate

import datetime
def getDate():
    now = datetime.datetime.now()
    return "%d/%d/%d" % (now.day, now.month, now.year)

@bold
def getBoldDate(): 
    return getDate()

@uk
def getUkDate():
    return getDate()

@bold
@uk
def getBoldUkDate():
    return getDate()


print getDate()
print getBoldDate()
print getUkDate()
print getBoldUkDate()
# what is happening under the covers
print bold(uk(getDate))()

输出如下:

17/6/2013
<b>17/6/2013</b>
6/17/2013
<b>6/17/2013</b>
<b>6/17/2013</b>

Here is a simple example of chaining decorators. Note the last line – it shows what is going on under the covers.

############################################################
#
#    decorators
#
############################################################

def bold(fn):
    def decorate():
        # surround with bold tags before calling original function
        return "<b>" + fn() + "</b>"
    return decorate


def uk(fn):
    def decorate():
        # swap month and day
        fields = fn().split('/')
        date = fields[1] + "/" + fields[0] + "/" + fields[2]
        return date
    return decorate

import datetime
def getDate():
    now = datetime.datetime.now()
    return "%d/%d/%d" % (now.day, now.month, now.year)

@bold
def getBoldDate(): 
    return getDate()

@uk
def getUkDate():
    return getDate()

@bold
@uk
def getBoldUkDate():
    return getDate()


print getDate()
print getBoldDate()
print getUkDate()
print getBoldUkDate()
# what is happening under the covers
print bold(uk(getDate))()

The output looks like:

17/6/2013
<b>17/6/2013</b>
6/17/2013
<b>6/17/2013</b>
<b>6/17/2013</b>

回答 14

说到计数器示例-如上所述,计数器将在使用装饰器的所有函数之间共享:

def counter(func):
    def wrapped(*args, **kws):
        print 'Called #%i' % wrapped.count
        wrapped.count += 1
        return func(*args, **kws)
    wrapped.count = 0
    return wrapped

这样,您的装饰器可以重用于不同的功能(或多次用于装饰相同的功能:)func_counter1 = counter(func); func_counter2 = counter(func),并且counter变量将对每个函数保持私有。

Speaking of the counter example – as given above, the counter will be shared between all functions that use the decorator:

def counter(func):
    def wrapped(*args, **kws):
        print 'Called #%i' % wrapped.count
        wrapped.count += 1
        return func(*args, **kws)
    wrapped.count = 0
    return wrapped

That way, your decorator can be reused for different functions (or used to decorate the same function multiple times: func_counter1 = counter(func); func_counter2 = counter(func)), and the counter variable will remain private to each.


回答 15

用不同数量的参数装饰函数:

def frame_tests(fn):
    def wrapper(*args):
        print "\nStart: %s" %(fn.__name__)
        fn(*args)
        print "End: %s\n" %(fn.__name__)
    return wrapper

@frame_tests
def test_fn1():
    print "This is only a test!"

@frame_tests
def test_fn2(s1):
    print "This is only a test! %s" %(s1)

@frame_tests
def test_fn3(s1, s2):
    print "This is only a test! %s %s" %(s1, s2)

if __name__ == "__main__":
    test_fn1()
    test_fn2('OK!')
    test_fn3('OK!', 'Just a test!')

结果:

Start: test_fn1  
This is only a test!  
End: test_fn1  


Start: test_fn2  
This is only a test! OK!  
End: test_fn2  


Start: test_fn3  
This is only a test! OK! Just a test!  
End: test_fn3  

Decorate functions with different number of arguments:

def frame_tests(fn):
    def wrapper(*args):
        print "\nStart: %s" %(fn.__name__)
        fn(*args)
        print "End: %s\n" %(fn.__name__)
    return wrapper

@frame_tests
def test_fn1():
    print "This is only a test!"

@frame_tests
def test_fn2(s1):
    print "This is only a test! %s" %(s1)

@frame_tests
def test_fn3(s1, s2):
    print "This is only a test! %s %s" %(s1, s2)

if __name__ == "__main__":
    test_fn1()
    test_fn2('OK!')
    test_fn3('OK!', 'Just a test!')

Result:

Start: test_fn1  
This is only a test!  
End: test_fn1  


Start: test_fn2  
This is only a test! OK!  
End: test_fn2  


Start: test_fn3  
This is only a test! OK! Just a test!  
End: test_fn3  

回答 16

保罗·贝尔甘蒂诺(Paolo Bergantino)的答案具有仅使用stdlib的巨大优势,并且适用于此简单示例,其中既没有装饰器参数,也没有装饰的函数参数。

但是,如果要处理更一般的情况,它有3个主要限制:

  • 正如已经在几个答案中指出的那样,您无法轻松地修改代码以添加可选的decorator参数。例如,创建makestyle(style='bold')装饰器并非易事。
  • 此外,使用创建的包装器@functools.wraps 不会保留签名,因此,如果提供了错误的参数,它们将开始执行,并且可能会引发与通常情况不同的错误TypeError
  • 最后,它是与创建包装相当困难@functools.wraps,以访问基于其名称的参数。实际上,该参数可以出现在*args,中**kwargs或完全不出现(如果它是可选的)。

我写decopatch来解决第一个问题,并写makefun.wraps来解决其他两个问题。请注意,makefun该方法与著名的decoratorlib 具有相同的技巧。

这是使用参数创建装饰器的方式,返回真正的保留签名的包装器:

from decopatch import function_decorator, DECORATED
from makefun import wraps

@function_decorator
def makestyle(st='b', fn=DECORATED):
    open_tag = "<%s>" % st
    close_tag = "</%s>" % st

    @wraps(fn)
    def wrapped(*args, **kwargs):
        return open_tag + fn(*args, **kwargs) + close_tag

    return wrapped

decopatch根据您的喜好,为您提供了另外两种隐藏或显示各种python概念的开发样式。最紧凑的样式如下:

from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS

@function_decorator
def makestyle(st='b', fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
    open_tag = "<%s>" % st
    close_tag = "</%s>" % st
    return open_tag + fn(*f_args, **f_kwargs) + close_tag

在这两种情况下,您都可以检查装饰器是否按预期工作:

@makestyle
@makestyle('i')
def hello(who):
    return "hello %s" % who

assert hello('world') == '<b><i>hello world</i></b>'    

有关详细信息,请参阅文档

Paolo Bergantino’s answer has the great advantage of only using the stdlib, and works for this simple example where there are no decorator arguments nor decorated function arguments.

However it has 3 major limitations if you want to tackle more general cases:

  • as already noted in several answers, you can not easily modify the code to add optional decorator arguments. For example creating a makestyle(style='bold') decorator is non-trivial.
  • besides, wrappers created with @functools.wraps do not preserve the signature, so if bad arguments are provided they will start executing, and might raise a different kind of error than the usual TypeError.
  • finally, it is quite difficult in wrappers created with @functools.wraps to access an argument based on its name. Indeed the argument can appear in *args, in **kwargs, or may not appear at all (if it is optional).

I wrote decopatch to solve the first issue, and wrote makefun.wraps to solve the other two. Note that makefun leverages the same trick than the famous decorator lib.

This is how you would create a decorator with arguments, returning truly signature-preserving wrappers:

from decopatch import function_decorator, DECORATED
from makefun import wraps

@function_decorator
def makestyle(st='b', fn=DECORATED):
    open_tag = "<%s>" % st
    close_tag = "</%s>" % st

    @wraps(fn)
    def wrapped(*args, **kwargs):
        return open_tag + fn(*args, **kwargs) + close_tag

    return wrapped

decopatch provides you with two other development styles that hide or show the various python concepts, depending on your preferences. The most compact style is the following:

from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS

@function_decorator
def makestyle(st='b', fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
    open_tag = "<%s>" % st
    close_tag = "</%s>" % st
    return open_tag + fn(*f_args, **f_kwargs) + close_tag

In both cases you can check that the decorator works as expected:

@makestyle
@makestyle('i')
def hello(who):
    return "hello %s" % who

assert hello('world') == '<b><i>hello world</i></b>'    

Please refer to the documentation for details.