标签归档:python-decorators

我如何避免“ self.x = x; self.y = y; __init__中的self.z = z”模式?

问题:我如何避免“ self.x = x; self.y = y; __init__中的self.z = z”模式?

我看到像

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

非常频繁,通常带有更多参数。是否有避免这种乏味重复的好方法?该类应该继承namedtuple吗?

I see patterns like

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

quite frequently, often with a lot more parameters. Is there a good way to avoid this type of tedious repetitiveness? Should the class inherit from namedtuple instead?


回答 0

编辑:如果您有python 3.7+,只需使用数据类

保留签名的装饰器解决方案:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

Edit: If you have python 3.7+ just use dataclasses

A decorator solution that keeps the signature:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

回答 1

免责声明:似乎有些人担心提出此解决方案,因此我将提供一个非常明确的免责声明。您不应该使用此解决方案。我仅将其作为信息提供,因此您知道该语言可以做到这一点。剩下的答案只是显示语言功能,而不是认可以这种方式使用它们。


明确地将参数复制到属性中并没有什么错。如果ctor中的参数太多,有时会被认为是代码异味,也许您应该将这些参数分组到更少的对象中。在其他时候,这是必要的,没有错。无论如何,明确地做到这一点是必须的。

但是,由于您要问如何完成(而不是是否应该这样做),因此一种解决方案是:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2

Disclaimer: It seems that several people are concerned about presenting this solution, so I will provide a very clear disclaimer. You should not use this solution. I only provide it as information, so you know that the language is capable of this. The rest of the answer is just showing language capabilities, not endorsing using them in this way.


There isn’t really anything wrong with explicitly copying parameters into attributes. If you have too many parameters in the ctor, it is sometimes considered a code smell and maybe you should group these params into a fewer objects. Other times, it is necessary and there is nothing wrong with it. Anyway, doing it explicitly is the way to go.

However, since you are asking HOW it can be done (and not whether it should be done), then one solution is this:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2

回答 2

正如其他人所提到的,重复并不坏,但在某些情况下,命名元组可能非常适合此类问题。这样可以避免使用locals()或kwargs,这通常不是一个好主意。

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

我发现它的用途有限,但是您可以像其他任何对象一样继承一个namedtuple(示例继续):

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

As others have mentioned, the repetition isn’t bad, but in some cases a namedtuple can be a great fit for this type of issue. This avoids using locals() or kwargs, which are usually a bad idea.

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

I’ve found limited use for it, but you can inherit a namedtuple as with any other object (example continued):

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

回答 3

显式比隐式更好…因此,请确保您可以使其更简洁:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

更好的问题是您?

…这就是说,如果您想要一个命名元组,我建议您使用namedtuple(记住元组具有某些附加条件)…也许您想要一个有序的字典甚至是一个字典…

explicit is better than implicit … so sure you could make it more concise:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

The better question is should you?

… that said if you want a named tuple I would recommend using a namedtuple (remember tuples have certain conditions attached to them) … perhaps you want an ordereddict or even just a dict …


回答 4

为了扩展gruszczys的答案,我使用了类似的模式:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

我喜欢这种方法,因为它:

  • 避免重复
  • 构造对象时可以抵抗拼写错误
  • 可以很好地与子类化(只需super().__init(...)
  • 允许在类级别(它们所属的地方)而不是在 X.__init__

在Python 3.6之前,这无法控制属性的设置顺序,如果某些属性是带有访问其他属性的设置器的属性,则可能会出现问题。

可能会有所改善,但是我是我自己的代码的唯一用户,因此我不担心任何形式的输入卫生。也许AttributeError更合适。

To expand on gruszczys answer, I have used a pattern like:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

I like this method because it:

  • avoids repetition
  • is resistant against typos when constructing an object
  • works well with subclassing (can just super().__init(...))
  • allows for documentation of the attributes on a class-level (where they belong) rather than in X.__init__

Prior to Python 3.6, this gives no control over the order in which the attributes are set, which could be a problem if some attributes are properties with setters that access other attributes.

It could probably be improved upon a bit, but I’m the only user of my own code so I am not worried about any form of input sanitation. Perhaps an AttributeError would be more appropriate.


回答 5

您也可以这样做:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

当然,您将必须导入inspect模块。

You could also do:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

Of course, you would have to import the inspect module.


回答 6

这是一个无需任何其他导入的解决方案。

辅助功能

一个小的辅助函数使它更加方便和可重复使用:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

应用

您需要使用以下命令调用它locals()

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

测试

a = A(1, 2, 3)
print(a.__dict__)

输出:

{'y': 2, 'z': 3, 'x': 1}

不变 locals()

如果您不想更改,请locals()使用以下版本:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)

This is a solution without any additional imports.

Helper function

A small helper function makes it more convenient and re-usable:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

Application

You need to call it with locals():

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

Test

a = A(1, 2, 3)
print(a.__dict__)

Output:

{'y': 2, 'z': 3, 'x': 1}

Without changing locals()

If you don’t like to change locals() use this version:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)

回答 7

一个有趣的库可以处理这个问题(并避免很多其他样板文件)是attrs。例如,您的示例可以简化为以下示例(假设该类称为MyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

您甚至不需要任何__init__方法,除非它也执行其他操作。这是Glyph Lefkowitz的精彩介绍

An interesting library that handles this (and avoids a lot of other boilerplate) is attrs. Your example, for instance, could be reduced to this (assume the class is called MyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

You don’t even need an __init__ method anymore, unless it does other stuff as well. Here’s a nice introduction by Glyph Lefkowitz.


回答 8

我的0.02 $。它与Joran Beasley的答案非常接近,但更为优雅:

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

此外,可以使用以下技术来减少MikeMüller的答案(最适合我的口味):

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

auto_init(locals())您的来话__init__

My 0.02$. It is very close to Joran Beasley answer, but more elegant:

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

Additionally, Mike Müller’s answer (the best one to my taste) can be reduced with this technique:

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

And the just call auto_init(locals()) from your __init__


回答 9

这是用Python做事的自然方法。不要尝试发明更聪明的东西,它会导致代码太聪明,而团队中没人会理解。如果您想成为团队合作者,然后继续以这种方式编写。

It’s a natural way to do things in Python. Don’t try to invent something more clever, it will lead to overly clever code that no one on your team will understand. If you want to be a team player and then keep writing it this way.


回答 10

Python 3.7以上

在Python 3.7中,您可以(ab)使用模块dataclass提供的装饰器dataclasses。从文档中:

该模块提供了一个装饰器和一些函数,用于自动将生成的特殊方法(例如__init__()和)添加__repr__()到用户定义的类中。它最初在PEP 557中进行了描述。

这些生成的方法中使用的成员变量是使用PEP 526类型注释定义的。例如此代码:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

除其他外,将添加__init__()如下所示的:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

请注意,此方法会自动添加到类中:上面显示的InventoryItem定义中未直接指定此方法。

如果您的类又大又复杂,那么使用可能是不合适的dataclass。我在Python 3.7.0发行之日就在写这篇文章,因此用法模式尚未很好地建立。

Python 3.7 onwards

In Python 3.7, you may (ab)use the dataclass decorator, available from the dataclasses module. From the documentation:

This module provides a decorator and functions for automatically adding generated special methods such as __init__() and __repr__() to user-defined classes. It was originally described in PEP 557.

The member variables to use in these generated methods are defined using PEP 526 type annotations. For example this code:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Will add, among other things, a __init__() that looks like:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

Note that this method is automatically added to the class: it is not directly specified in the InventoryItem definition shown above.

If your class is large and complex, it may be inappropriate to use a dataclass. I’m writing this on the day of release of Python 3.7.0, so usage patterns are not yet well established.


具有自变量的类方法修饰器?

问题:具有自变量的类方法修饰器?

如何将类字段作为参数传递给类方法的装饰器?我想做的是这样的:

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization("some_attr", self.url)
    def get(self):
        do_work()

它抱怨自己不存在传递self.url给装饰器。有没有解决的办法?

How do I pass a class field to a decorator on a class method as an argument? What I want to do is something like:

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization("some_attr", self.url)
    def get(self):
        do_work()

It complains that self does not exist for passing self.url to the decorator. Is there a way around this?


回答 0

是。与其在类定义时传递实例属性,不如在运行时检查它:

def check_authorization(f):
    def wrapper(*args):
        print args[0].url
        return f(*args)
    return wrapper

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization
    def get(self):
        print 'get'

>>> Client('http://www.google.com').get()
http://www.google.com
get

装饰器截取方法参数;第一个参数是实例,因此它从中读取属性。您可以将属性名称作为字符串传递给装饰器,并getattr在不想对属性名称进行硬编码时使用:

def check_authorization(attribute):
    def _check_authorization(f):
        def wrapper(self, *args):
            print getattr(self, attribute)
            return f(self, *args)
        return wrapper
    return _check_authorization

Yes. Instead of passing in the instance attribute at class definition time, check it at runtime:

def check_authorization(f):
    def wrapper(*args):
        print args[0].url
        return f(*args)
    return wrapper

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization
    def get(self):
        print 'get'

>>> Client('http://www.google.com').get()
http://www.google.com
get

The decorator intercepts the method arguments; the first argument is the instance, so it reads the attribute off of that. You can pass in the attribute name as a string to the decorator and use getattr if you don’t want to hardcode the attribute name:

def check_authorization(attribute):
    def _check_authorization(f):
        def wrapper(self, *args):
            print getattr(self, attribute)
            return f(self, *args)
        return wrapper
    return _check_authorization

回答 1

from re import search
from functools import wraps

def is_match(_lambda, pattern):
    def wrapper(f):
        @wraps(f)
        def wrapped(self, *f_args, **f_kwargs):
            if callable(_lambda) and search(pattern, (_lambda(self) or '')): 
                f(self, *f_args, **f_kwargs)
        return wrapped
    return wrapper

class MyTest(object):

    def __init__(self):
        self.name = 'foo'
        self.surname = 'bar'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'foo')
    def my_rule(self):
        print 'my_rule : ok'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'bar')
    def my_rule2(self):
        print 'my_rule2 : ok'



test = MyTest()
test.my_rule()
test.my_rule2()

输出:my_rule2:好的

from re import search
from functools import wraps

def is_match(_lambda, pattern):
    def wrapper(f):
        @wraps(f)
        def wrapped(self, *f_args, **f_kwargs):
            if callable(_lambda) and search(pattern, (_lambda(self) or '')): 
                f(self, *f_args, **f_kwargs)
        return wrapped
    return wrapper

class MyTest(object):

    def __init__(self):
        self.name = 'foo'
        self.surname = 'bar'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'foo')
    def my_rule(self):
        print 'my_rule : ok'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'bar')
    def my_rule2(self):
        print 'my_rule2 : ok'



test = MyTest()
test.my_rule()
test.my_rule2()

ouput: my_rule2 : ok


回答 2

更为简洁的示例如下:

#/usr/bin/env python3
from functools import wraps

def wrapper(method):
    @wraps(method)
    def _impl(self, *method_args, **method_kwargs):
        method_output = method(self, *method_args, **method_kwargs)
        return method_output + "!"
    return _impl

class Foo:
    @wrapper
    def bar(self, word):
        return word

f = Foo()
result = f.bar("kitty")
print(result)

将打印:

kitty!

A more concise example might be as follows:

#/usr/bin/env python3
from functools import wraps

def wrapper(method):
    @wraps(method)
    def _impl(self, *method_args, **method_kwargs):
        method_output = method(self, *method_args, **method_kwargs)
        return method_output + "!"
    return _impl

class Foo:
    @wrapper
    def bar(self, word):
        return word

f = Foo()
result = f.bar("kitty")
print(result)

Which will print:

kitty!

回答 3

另一个选择是放弃语法糖,并在__init__该类中进行装饰。

def countdown(number):
    def countdown_decorator(func):
        def func_wrapper():
            for index in reversed(range(1, number+1)):
                print("{}".format(index))
            func()
        return func_wrapper
    return countdown_decorator

class MySuperClass():
    def __init__(self, number):
        self.number = number
        self.do_thing = countdown(number)(self.do_thing)

    def do_thing(self):
        print('im doing stuff!')


myclass = MySuperClass(3)

myclass.do_thing()

将打印

3
2
1
im doing stuff!

Another option would be to abandon the syntactic sugar and decorate in the __init__ of the class.

def countdown(number):
    def countdown_decorator(func):
        def func_wrapper():
            for index in reversed(range(1, number+1)):
                print("{}".format(index))
            func()
        return func_wrapper
    return countdown_decorator

class MySuperClass():
    def __init__(self, number):
        self.number = number
        self.do_thing = countdown(number)(self.do_thing)

    def do_thing(self):
        print('im doing stuff!')


myclass = MySuperClass(3)

myclass.do_thing()

which would print

3
2
1
im doing stuff!

回答 4

你不能 有没有self在类体内,因为没有实例存在。您需要传递它,例如,一个str包含要在实例上查找的属性名称,然后返回的函数可以执行此操作,或者完全使用其他方法。

You can’t. There’s no self in the class body, because no instance exists. You’d need to pass it, say, a str containing the attribute name to lookup on the instance, which the returned function can then do, or use a different method entirely.


关于如何在python中使用属性功能的真实示例?

问题:关于如何在python中使用属性功能的真实示例?

我对如何@property在Python中使用感兴趣。我阅读了python文档,并认为其中的示例只是一个玩具代码:

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用属性装饰器填充的内容可以获得什么好处。为什么不只是实现为:

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

我认为,属性功能在某些情况下可能很有用。但当?有人可以给我一些真实的例子吗?

谢谢。

I am interested in how to use @property in Python. I’ve read the python docs and the example there, in my opinion, is just a toy code:

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

I do not know what benefit(s) I can get from wrapping the _x filled with the property decorator. Why not just implement as:

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

I think, the property feature might be useful in some situations. But when? Could someone please give me some real-world examples?

Thanks.


回答 0

其他示例是对设置的属性进行验证/过滤(强制将它们限制或接受)以及对复杂或快速变化的术语进行惰性评估。

隐藏在属性后面的复杂计算:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

验证:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

Other examples would be validation/filtering of the set attributes (forcing them to be in bounds or acceptable) and lazy evaluation of complex or rapidly changing terms.

Complex calculation hidden behind an attribute:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validation:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

回答 1

一个简单的用例是设置一个只读实例属性,因为您知道_x在python 中用一个下划线开头一个变量名通常意味着它是私有的(内部使用),但是有时我们希望能够读取实例属性而不是编写所以我们可以使用property它:

>>> class C(object):

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

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

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

One simple use case will be to set a read only instance attribute , as you know leading a variable name with one underscore _x in python usually mean it’s private (internal use) but sometimes we want to be able to read the instance attribute and not to write it so we can use property for this:

>>> class C(object):

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

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

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

回答 2

看一下这篇文章,将其用于非常实际的用途。简而言之,它解释了在Python中通常如何放弃显式的getter / setter方法,因为如果在某个阶段需要它们,则可以使用它property来实现无缝实现。

Take a look at this article for a very practical use. In short, it explains how in Python you can usually ditch explicit getter/setter method, since if you come to need them at some stage you can use property for a seamless implementation.


回答 3

我使用它的一件事是缓存存储在数据库中的查找缓慢但不变的值。这会泛化到您的属性需要计算或您只想按需执行的其他长时间操作(例如数据库检查,网络通信)的任何情况。

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

这是在一个Web应用程序中,其中任何给定的页面视图可能只需要一个这样的特定属性,但是基础对象本身可能具有几个这样的属性-在构造时将它们全部初始化将是浪费的,而属性使我可以灵活地使用属性是惰性的,而不是。

One thing I’ve used it for is caching slow-to-look-up, but unchanging, values stored in a database. This generalises to any situation where your attributes require computation or some other long operation (eg. database check, network communication) which you only want to do on demand.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

This was in a web app where any given page view might only need one particular attribute of this kind, but the underlying objects themselves might have several such attributes – initialising them all on construction would be wasteful, and properties allow me to be flexible in which attributes are lazy and which aren’t.


回答 4

通读答案和评论,主题似乎是答案似乎缺少一个简单但有用的示例。我在这里包括了一个非常简单的示例,演示了@property装饰器的简单用法。该类允许用户使用各种不同的单位(即in_feet或)指定并获取距离测量值in_metres

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

用法:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

Reading through the answers and comments, the main theme seems to be the answers seem to be missing a simple, yet useful example. I have included a very simple one here that demonstrates the simple use of the @property decorator. It’s a class that allows a user to specify and get distance measurement using a variety of different units, i.e. in_feet or in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Usage:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

回答 5

属性只是字段的抽象,它使您可以更好地控制特定字段的操作方式和中间件计算。想到的用法很少是验证,事先初始化和访问限制

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

Property is just an abstraction around a field which give you more control on ways that a specific field can be manipulated and to do middleware computations. Few of the usages that come to mind is validation and prior initialization and access restriction

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

回答 6

是的,对于发布的原始示例,该属性的作用与仅具有实例变量“ x”的作用完全相同。

这是关于python属性的最好的事情。从外部看,它们的工作方式完全类似于实例变量!这允许您从类外部使用实例变量。

这意味着您的第一个示例实际上可以使用实例变量。如果情况发生了变化,然后您决定更改实现,并且一个属性很有用,则该类的代码与该类外部的代码的接口将相同。 从实例变量更改为属性不会影响类外部的代码。

许多其他语言和编程类将指示程序员不要公开实例变量,而应使用“ getters”和“ setters”从类外部访问任何值,即使是问题中引用的简单情况也是如此。

类外的代码使用多种语言(例如Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

在实现该类时,有许多“ getter”和“ setter”的作用与您的第一个示例完全相同:复制一个简单的实例变量。这些获取器和设置器是必需的,因为如果类实现更改,则该类外部的所有代码都需要更改。但是python属性允许类外的代码与实例变量相同。因此,如果添加属性或具有简单的实例变量,则无需更改类外部的代码。因此,与大多数面向对象的语言不同,对于您的简单示例,您可以使用实例变量代替真正不需要的“ getters”和“ setters”,因此请确保在将来更改为属性时,可以使用您的Class无需更改。

这意味着仅当行为复杂时才需要创建属性,对于非常普遍的简单情况(如问题中所述),仅需要一个简单的实例变量,您只需使用实例变量即可。

Yes, for the original example posted, the property will work exactly the same as simply having an instance variable ‘x’.

This is the best thing about python properties. From the outside, they work exactly like instance variables! Which allows you to use instance variables from outside the class.

This means your first example could actually use an instance variable. If things changed, and then you decide to change your implementation and a property is useful, the interface to the property would still be the same from code outside the class. A change from instance variable to property has no impact on code outside the class.

Many other languages and programming courses will instruct that a programmer should never expose instance variables, and instead use ‘getters’ and ‘setters’ for any value to be accessed from outside the class, even the simple case as quoted in the question.

Code outside the class with many languages (e.g. Java) use

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

And when implementing the class there are many ‘getters’ and ‘setters’ that do exactly as your first example: replicate a simply instance variable. These getters and setters are required because if the class implementation changes, all the code outside the class will need to change. But python properties allow code outside the class to be the same as with instance variables. So code outside the class does not need to be changed if you add a property, or have a simple instance variable. So unlike most Object Oriented languages, for your simple example you can use the instance variable instead of ‘getters’ and ‘setters’ that are really not needed, secure in the knowledge that if you change to a property in the future, the code using your class need not change.

This means you only need create properties if there is complex behaviour, and for the very common simple case where, as described in the question, a simple instance variable is all that is needed, you can just use the instance variable.


回答 7

与使用setter和getters相比,属性的另一个不错的功能是,它们允许您继续在属性上使用OP =运算符(例如,+ =,-=,* =等),同时仍保留任何验证,访问控制,缓存等设置者和获取者将提供。

例如,如果您Person使用setter setage(newage)和getter 编写了类,getage()则要增加年龄,您必须编写:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

但是如果您age有财产,您可以写得更加简洁:

bob.age += 1

another nice feature of properties over using setters and getters it that they allow you to continue to use OP= operators (eg +=, -=, *= etc) on your attributes while still retaining any validation, access control, caching, etc that the setters and getters would supply.

for example if you wrote the class Person with a setter setage(newage), and a getter getage(), then to increment the age you would have to write:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

but if you made age a property you could write the much cleaner:

bob.age += 1

回答 8

这个问题的简短答案是,在您的示例中,没有任何好处。您可能应该使用不包含属性的表格。

属性存在的原因是,如果您的代码在将来发生更改,并且您突然需要对数据做更多的事情:缓存值,保护访问,查询一些外部资源…等等,您可以轻松地修改类以添加getter和数据的设置器,而无需更改接口,因此您不必在代码中的任何地方找到要访问该数据的地方,也不必进行更改。

The short answer to your question, is that in your example, there is no benefit. You should probably use the form that doesn’t involve properties.

The reason properties exists, is that if your code changes in the future, and you suddenly need to do more with your data: cache values, protect access, query some external resource… whatever, you can easily modify your class to add getters and setters for the data without changing the interface, so you don’t have to find everywhere in your code where that data is accessed and change that too.


回答 9

起初许多人没有注意到的事情是您可以创建自己的属性子类。我发现这对于公开只读对象属性或可以读写但不能删除的属性非常有用。这也是包装诸如跟踪对对象字段的修改之类的功能的绝佳方法。

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

Something that many do not notice at first is that you can make your own subclasses of property. This I have found very useful for exposing read only object attributes or attribute you can read and write but not remove. It is also an excellent way to wrap functionality like tracking modifications to object fields.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

Python中的“ @”(@)符号有什么作用?

问题:Python中的“ @”(@)符号有什么作用?

我正在看一些使用 @符号的,但我不知道它的作用。我也不清楚要搜索的内容,因为搜索Python文档时会发现该@符号,否则Google不会返回相关结果。

I’m looking at some Python code which used the @ symbol, but I have no idea what it does. I also do not know what to search for as searching Python docs or Google does not return relevant results when the @ symbol is included.


回答 0

行首的@符号用于类,函数和方法修饰符

在这里阅读更多:

PEP 318:装饰器

Python装饰器

您会遇到的最常见的Python装饰器是:

@属性

@classmethod

@staticmethod

如果您@在一行的中间看到一个,那就是矩阵乘法。向下滚动以查看其他解决使用的答案@

An @ symbol at the beginning of a line is used for class, function and method decorators.

Read more here:

PEP 318: Decorators

Python Decorators

The most common Python decorators you’ll run into are:

@property

@classmethod

@staticmethod

If you see an @ in the middle of a line, that’s a different thing, matrix multiplication. Scroll down to see other answers that address that use of @.


回答 1

class Pizza(object):
    def __init__(self):
        self.toppings = []

    def __call__(self, topping):
        # When using '@instance_of_pizza' before a function definition
        # the function gets passed onto 'topping'.
        self.toppings.append(topping())

    def __repr__(self):
        return str(self.toppings)

pizza = Pizza()

@pizza
def cheese():
    return 'cheese'
@pizza
def sauce():
    return 'sauce'

print pizza
# ['cheese', 'sauce']

这表明在修饰符之后定义的function/ method/ 基本上只是作为符号传递到/ 之后的/ 。classargumentfunctionmethod@

第一次发现

微框架Flask从一开始就以以下格式引入装饰器

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

依次将其翻译为:

rule      = "/"
view_func = hello
# They go as arguments here in 'flask/app.py'
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    pass

意识到这一点终于使我与Flask和平相处。

Example

class Pizza(object):
    def __init__(self):
        self.toppings = []

    def __call__(self, topping):
        # When using '@instance_of_pizza' before a function definition
        # the function gets passed onto 'topping'.
        self.toppings.append(topping())

    def __repr__(self):
        return str(self.toppings)

pizza = Pizza()

@pizza
def cheese():
    return 'cheese'
@pizza
def sauce():
    return 'sauce'

print pizza
# ['cheese', 'sauce']

This shows that the function/method/class you’re defining after a decorator is just basically passed on as an argument to the function/method immediately after the @ sign.

First sighting

The microframework Flask introduces decorators from the very beginning in the following format:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

This in turn translates to:

rule      = "/"
view_func = hello
# They go as arguments here in 'flask/app.py'
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    pass

Realizing this finally allowed me to feel at peace with Flask.


回答 2

此代码段:

def decorator(func):
   return func

@decorator
def some_func():
    pass

等效于以下代码:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

在装饰器的定义中,您可以添加一些通常不会由函数返回的修改内容。

This code snippet:

def decorator(func):
   return func

@decorator
def some_func():
    pass

Is equivalent to this code:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

In the definition of a decorator you can add some modified things that wouldn’t be returned by a function normally.


回答 3

在Python 3.5中,您可以重载@为运算符。之所以命名为__matmul__,是因为它被设计用于进行矩阵乘法,但是它可以是任何您想要的。有关详细信息,请参见PEP465

这是矩阵乘法的简单实现。

class Mat(list):
    def __matmul__(self, B):
        A = self
        return Mat([[sum(A[i][k]*B[k][j] for k in range(len(B)))
                    for j in range(len(B[0])) ] for i in range(len(A))])

A = Mat([[1,3],[7,5]])
B = Mat([[6,8],[4,2]])

print(A @ B)

此代码生成:

[[18, 14], [62, 66]]

In Python 3.5 you can overload @ as an operator. It is named as __matmul__, because it is designed to do matrix multiplication, but it can be anything you want. See PEP465 for details.

This is a simple implementation of matrix multiplication.

class Mat(list):
    def __matmul__(self, B):
        A = self
        return Mat([[sum(A[i][k]*B[k][j] for k in range(len(B)))
                    for j in range(len(B[0])) ] for i in range(len(A))])

A = Mat([[1,3],[7,5]])
B = Mat([[6,8],[4,2]])

print(A @ B)

This code yields:

[[18, 14], [62, 66]]

回答 4

Python中的“ @”(@)符号有什么作用?

简而言之,它用于装饰器语法和矩阵乘法。

在装饰器的上下文中,此语法为:

@decorator
def decorated_function():
    """this function is decorated"""

等效于此:

def decorated_function():
    """this function is decorated"""

decorated_function = decorator(decorated_function)

在矩阵乘法的上下文中,a @ b调用a.__matmul__(b)-使用以下语法:

a @ b

相当于

dot(a, b)

a @= b

相当于

a = dot(a, b)

其中dot,例如是numpy矩阵乘法函数,并且aand b是矩阵。

您如何独自发现呢?

我也不知道要搜索什么,因为搜索Python文档时会出现,或者当包含@符号时Google不会返回相关结果。

如果您想对某个特定的python语法有一个比较完整的了解,请直接查看语法文件。对于Python 3分支:

~$ grep -C 1 "@" cpython/Grammar/Grammar 

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
--
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
            '<<=' | '>>=' | '**=' | '//=')
--
arith_expr: term (('+'|'-') term)*
term: factor (('*'|'@'|'/'|'%'|'//') factor)*
factor: ('+'|'-'|'~') factor | power

我们可以在这里看到@在三种情况下使用的内容:

  • 装饰工
  • 因子之间的运算符
  • 扩充的赋值运算符

装饰语法:

在Google上搜索“ decorator python docs”时,将“ Python语言参考”的“复合语句”部分作为最高结果之一。向下滚动至函数定义部分,我们可以通过搜索“ decorator”一词找到该部分,我们发现……有很多东西可供阅读。但是“ decorator”这个词是词汇表的链接,它告诉我们:

装饰工

返回另一个函数的函数,通常使用@wrapper语法将其用作函数转换。装饰器的常见示例是classmethod()staticmethod()

装饰器语法只是语法糖,以下两个函数定义在语义上是等效的:

def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...

类存在相同的概念,但在该类中较少使用。有关装饰器的更多信息,请参见函数定义和类定义的文档。

所以,我们看到

@foo
def bar():
    pass

在语义上与:

def bar():
    pass

bar = foo(bar)

它们并不完全相同,因为Python在装饰器(@)语法之前在bar之前评估foo表达式(可能是点分查找和函数调用),但在另一种情况下,则 bar 之后评估foo表达式。

(如果这种差异使代码的含义有所不同,则应重新考虑自己的生活,因为那会是病态的。)

堆叠式装饰器

如果我们回到函数定义语法文档,则会看到:

@f1(arg)
@f2
def func(): pass

大致相当于

def func(): pass
func = f1(arg)(f2(func))

这是一个演示,我们可以调用首先是装饰器的函数以及堆栈装饰器。在Python中,函数是一流的对象-这意味着您可以将一个函数作为参数传递给另一个函数,然后返回函数。装饰者可以做这两种事情。

如果我们堆叠装饰器,则已定义的函数会首先传递到紧接其上的装饰器,然后传递给下一个,依此类推。

这就总结了@装饰器上下文中的用法。

运营商, @

在语言参考的词法分析部分,我们有一个关于运算符部分,其中包括@,这使得它同时也是一个运算符:

以下标记是运算符:

+       -       *       **      /       //      %      @
<<      >>      &       |       ^       ~
<       >       <=      >=      ==      !=

在下一页的数据模型中,我们有模拟数字类型一节,

object.__add__(self, other)
object.__sub__(self, other) 
object.__mul__(self, other) 
object.__matmul__(self, other) 
object.__truediv__(self, other) 
object.__floordiv__(self, other)

[…]这些方法称为执行二进制算术运算(+-*@///,[…]

我们看到__matmul__对应于@。如果我们在文档中搜索“ matmul” ,则会在标题“ PEP 465-矩阵乘法的专用中缀运算符”下获得指向“ matmul”的Python 3.5新增功能的链接。

可以通过定义__matmul__()__rmatmul__()以及 __imatmul__()常规,反射和就地矩阵乘法来实现。

(所以现在我们了解到它@=是就地版本)。它进一步说明:

在数学,科学,工程学的许多领域中,矩阵乘法是一种常见的操作,并且@的加法允许编写更简洁的代码:

S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)

代替:

S = dot((dot(H, beta) - r).T,
        dot(inv(dot(dot(H, V), H.T)), dot(H, beta) - r))

尽管可以重载该运算符以执行几乎所有操作,numpy例如,在中,我们将使用以下语法来计算数组和矩阵的内乘和外乘:

>>> from numpy import array, matrix
>>> array([[1,2,3]]).T @ array([[1,2,3]])
array([[1, 2, 3],
       [2, 4, 6],
       [3, 6, 9]])
>>> array([[1,2,3]]) @ array([[1,2,3]]).T
array([[14]])
>>> matrix([1,2,3]).T @ matrix([1,2,3])
matrix([[1, 2, 3],
        [2, 4, 6],
        [3, 6, 9]])
>>> matrix([1,2,3]) @ matrix([1,2,3]).T
matrix([[14]])

就地矩阵乘法: @=

在研究现有用法时,我们了解到还有就地矩阵乘法。如果尝试使用它,我们可能会发现尚未为numpy实现它:

>>> m = matrix([1,2,3])
>>> m @= m.T
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: In-place matrix multiplication is not (yet) supported. Use 'a = a @ b' instead of 'a @= b'.

实施后,我希望结果看起来像这样:

>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])

What does the “at” (@) symbol do in Python?

In short, it is used in decorator syntax and for matrix multiplication.

In the context of decorators, this syntax:

@decorator
def decorated_function():
    """this function is decorated"""

is equivalent to this:

def decorated_function():
    """this function is decorated"""

decorated_function = decorator(decorated_function)

In the context of matrix multiplication, a @ b invokes a.__matmul__(b) – making this syntax:

a @ b

equivalent to

dot(a, b)

and

a @= b

equivalent to

a = dot(a, b)

where dot is, for example, the numpy matrix multiplication function and a and b are matrices.

How could you discover this on your own?

I also do not know what to search for as searching Python docs or Google does not return relevant results when the @ symbol is included.

If you want to have a rather complete view of what a particular piece of python syntax does, look directly at the grammar file. For the Python 3 branch:

~$ grep -C 1 "@" cpython/Grammar/Grammar 

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
--
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
            '<<=' | '>>=' | '**=' | '//=')
--
arith_expr: term (('+'|'-') term)*
term: factor (('*'|'@'|'/'|'%'|'//') factor)*
factor: ('+'|'-'|'~') factor | power

We can see here that @ is used in three contexts:

  • decorators
  • an operator between factors
  • an augmented assignment operator

Decorator Syntax:

A google search for “decorator python docs” gives as one of the top results, the “Compound Statements” section of the “Python Language Reference.” Scrolling down to the section on function definitions, which we can find by searching for the word, “decorator”, we see that… there’s a lot to read. But the word, “decorator” is a link to the glossary, which tells us:

decorator

A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().

The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:

def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...

The same concept exists for classes, but is less commonly used there. See the documentation for function definitions and class definitions for more about decorators.

So, we see that

@foo
def bar():
    pass

is semantically the same as:

def bar():
    pass

bar = foo(bar)

They are not exactly the same because Python evaluates the foo expression (which could be a dotted lookup and a function call) before bar with the decorator (@) syntax, but evaluates the foo expression after bar in the other case.

(If this difference makes a difference in the meaning of your code, you should reconsider what you’re doing with your life, because that would be pathological.)

Stacked Decorators

If we go back to the function definition syntax documentation, we see:

@f1(arg)
@f2
def func(): pass

is roughly equivalent to

def func(): pass
func = f1(arg)(f2(func))

This is a demonstration that we can call a function that’s a decorator first, as well as stack decorators. Functions, in Python, are first class objects – which means you can pass a function as an argument to another function, and return functions. Decorators do both of these things.

If we stack decorators, the function, as defined, gets passed first to the decorator immediately above it, then the next, and so on.

That about sums up the usage for @ in the context of decorators.

The Operator, @

In the lexical analysis section of the language reference, we have a section on operators, which includes @, which makes it also an operator:

The following tokens are operators:

+       -       *       **      /       //      %      @
<<      >>      &       |       ^       ~
<       >       <=      >=      ==      !=

and in the next page, the Data Model, we have the section Emulating Numeric Types,

object.__add__(self, other)
object.__sub__(self, other) 
object.__mul__(self, other) 
object.__matmul__(self, other) 
object.__truediv__(self, other) 
object.__floordiv__(self, other)

[…] These methods are called to implement the binary arithmetic operations (+, -, *, @, /, //, […]

And we see that __matmul__ corresponds to @. If we search the documentation for “matmul” we get a link to What’s new in Python 3.5 with “matmul” under a heading “PEP 465 – A dedicated infix operator for matrix multiplication”.

it can be implemented by defining __matmul__(), __rmatmul__(), and __imatmul__() for regular, reflected, and in-place matrix multiplication.

(So now we learn that @= is the in-place version). It further explains:

Matrix multiplication is a notably common operation in many fields of mathematics, science, engineering, and the addition of @ allows writing cleaner code:

S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)

instead of:

S = dot((dot(H, beta) - r).T,
        dot(inv(dot(dot(H, V), H.T)), dot(H, beta) - r))

While this operator can be overloaded to do almost anything, in numpy, for example, we would use this syntax to calculate the inner and outer product of arrays and matrices:

>>> from numpy import array, matrix
>>> array([[1,2,3]]).T @ array([[1,2,3]])
array([[1, 2, 3],
       [2, 4, 6],
       [3, 6, 9]])
>>> array([[1,2,3]]) @ array([[1,2,3]]).T
array([[14]])
>>> matrix([1,2,3]).T @ matrix([1,2,3])
matrix([[1, 2, 3],
        [2, 4, 6],
        [3, 6, 9]])
>>> matrix([1,2,3]) @ matrix([1,2,3]).T
matrix([[14]])

Inplace matrix multiplication: @=

While researching the prior usage, we learn that there is also the inplace matrix multiplication. If we attempt to use it, we may find it is not yet implemented for numpy:

>>> m = matrix([1,2,3])
>>> m @= m.T
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: In-place matrix multiplication is not (yet) supported. Use 'a = a @ b' instead of 'a @= b'.

When it is implemented, I would expect the result to look like this:

>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])

回答 5

Python中的“ @”(@)符号有什么作用?

@符号是python提供的语法糖decorator
以解释这个问题,这恰恰是关于decorator在Python中的作用?

简而言之,decorator您可以修改给定函数的定义,而无需触摸其最内层(即封闭)。
从第三方导入精美的包时,最常见的情况是。您可以可视化它,可以使用它,但不能触摸它的最内层和内心。

这是一个简单的示例,
假设我read_a_book在Ipython上定义了一个函数

In [9]: def read_a_book():
   ...:     return "I am reading the book: "
   ...: 
In [10]: read_a_book()
Out[10]: 'I am reading the book: '

你看,我忘了给它加一个名字。
如何解决这样的问题?当然,我可以将函数重新定义为:

def read_a_book():
    return "I am reading the book: 'Python Cookbook'"

但是,如果不允许我操作原始功能,或者要处理成千上万个此类功能,该怎么办。

通过不同的思维来解决问题并定义一个new_function

def add_a_book(func):
    def wrapper():
        return func() + "Python Cookbook"
    return wrapper

然后雇用它。

In [14]: read_a_book = add_a_book(read_a_book)
In [15]: read_a_book()
Out[15]: 'I am reading the book: Python Cookbook'

Tada,您知道,我在read_a_book不触及内部封闭的情况下进行了修改。没有什么可以阻止我装备decorator

关于什么 @

@add_a_book
def read_a_book():
    return "I am reading the book: "
In [17]: read_a_book()
Out[17]: 'I am reading the book: Python Cookbook'

@add_a_book这是一种花哨且方便的表达方式read_a_book = add_a_book(read_a_book),它是一种语法糖,没有比这更奇特的了。

What does the “at” (@) symbol do in Python?

@ symbol is a syntactic sugar python provides to utilize decorator,
to paraphrase the question, It’s exactly about what does decorator do in Python?

Put it simple decorator allow you to modify a given function’s definition without touch its innermost (it’s closure).
It’s the most case when you import wonderful package from third party. You can visualize it, you can use it, but you cannot touch its innermost and its heart.

Here is a quick example,
suppose I define a read_a_book function on Ipython

In [9]: def read_a_book():
   ...:     return "I am reading the book: "
   ...: 
In [10]: read_a_book()
Out[10]: 'I am reading the book: '

You see, I forgot to add a name to it.
How to solve such a problem? Of course, I could re-define the function as:

def read_a_book():
    return "I am reading the book: 'Python Cookbook'"

Nevertheless, what if I’m not allowed to manipulate the original function, or if there are thousands of such function to be handled.

Solve the problem by thinking different and define a new_function

def add_a_book(func):
    def wrapper():
        return func() + "Python Cookbook"
    return wrapper

Then employ it.

In [14]: read_a_book = add_a_book(read_a_book)
In [15]: read_a_book()
Out[15]: 'I am reading the book: Python Cookbook'

Tada, you see, I amended read_a_book without touching it inner closure. Nothing stops me equipped with decorator.

What’s about @

@add_a_book
def read_a_book():
    return "I am reading the book: "
In [17]: read_a_book()
Out[17]: 'I am reading the book: Python Cookbook'

@add_a_book is a fancy and handy way to say read_a_book = add_a_book(read_a_book), it’s a syntactic sugar, there’s nothing more fancier about it.


回答 6

如果您在使用Numpy库的python笔记本中引用某些代码,则@ operator表示矩阵乘法。例如:

import numpy as np
def forward(xi, W1, b1, W2, b2):
    z1 = W1 @ xi + b1
    a1 = sigma(z1)
    z2 = W2 @ a1 + b2
    return z2, a1

If you are referring to some code in a python notebook which is using Numpy library, then @ operator means Matrix Multiplication. For example:

import numpy as np
def forward(xi, W1, b1, W2, b2):
    z1 = W1 @ xi + b1
    a1 = sigma(z1)
    z2 = W2 @ a1 + b2
    return z2, a1

回答 7

从Python 3.5开始,“ @”用作MATRIX MULTIPLICATION(PEP 0465-参见https://www.python.org/dev/peps/pep-0465/)的专用中缀符号。

Starting with Python 3.5, the ‘@’ is used as a dedicated infix symbol for MATRIX MULTIPLICATION (PEP 0465 — see https://www.python.org/dev/peps/pep-0465/)


回答 8

在Python中添加了装饰器,以使函数和方法包装(接收函数并返回增强函数的函数)更易于阅读和理解。最初的用例是能够在定义的顶部将方法定义为类方法或静态方法。没有装饰器语法,将需要一个相当稀疏且重复的定义:

class WithoutDecorators:
def some_static_method():
    print("this is static method")
some_static_method = staticmethod(some_static_method)

def some_class_method(cls):
    print("this is class method")
some_class_method = classmethod(some_class_method)

如果将装饰器语法用于相同目的,则代码将更短且更易于理解:

class WithDecorators:
    @staticmethod
    def some_static_method():
        print("this is static method")

    @classmethod
    def some_class_method(cls):
        print("this is class method")

通用语法和可能的实现

装饰器通常是一个命名对象(不允许使用lambda表达式),该对象在被调用时将接受单个参数(它将成为装饰后的函数)并返回另一个可调用对象。此处使用“可调用”代替带有预想的“功能”。尽管装饰器通常在方法和功能的范围内进行讨论,但它们不限于此。实际上,任何可调用的对象(实现_call__方法的任何对象都被视为可调用对象)可以用作修饰符,并且它们返回的对象通常不是简单的函数,而是更多复杂类的实例,这些实例实现了自己的__call_方法。

装饰器语法只是一个语法糖。考虑以下装饰器用法:

@some_decorator
def decorated_function():
    pass

总是可以用显式的装饰器调用和函数重新分配来代替:

def decorated_function():
    pass
decorated_function = some_decorator(decorated_function)

但是,如果在单个函数上使用多个装饰器,则后者的可读性较低,并且也很难理解。可以以多种不同方式使用装饰器,如下所示:

作为功​​能

编写自定义装饰器的方法有很多,但是最简单的方法是编写一个函数,该函数返回包装原始函数调用的子函数。

通用模式如下:

def mydecorator(function):
    def wrapped(*args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result
    # return wrapper as a decorated function
    return wrapped

上课

尽管装饰器几乎总是可以使用函数来实现,但在某些情况下,使用用户定义的类是更好的选择。当装饰器需要复杂的参数化或取决于特定状态时,通常会发生这种情况。

非参数化装饰器作为类的通用模式如下:

class DecoratorAsClass:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = self.function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result

参数化装饰器

在实际代码中,经常需要使用可以参数化的装饰器。当将该函数用作装饰器时,解决方案很简单-必须使用第二层包装。这是装饰器的一个简单示例,该装饰器每次被调用都会重复执行装饰函数指定次数:

def repeat(number=3):
"""Cause decorated function to be repeated a number of times.

Last value of original function call is returned as a result
:param number: number of repetitions, 3 if not specified
"""
def actual_decorator(function):
    def wrapper(*args, **kwargs):
        result = None
        for _ in range(number):
            result = function(*args, **kwargs)
        return result
    return wrapper
return actual_decorator

通过这种方式定义的装饰器可以接受参数:

>>> @repeat(2)
... def foo():
...     print("foo")
...
>>> foo()
foo
foo

请注意,即使参数化装饰器的参数具有默认值,也必须在其名称后加上括号。使用带有默认参数的前面装饰器的正确方法如下:

>>> @repeat()
... def bar():
...     print("bar")
...
>>> bar()
bar
bar
bar

最后,让我们看看带有Properties的装饰器。

物产

这些属性提供了一个内置的描述符类型,该描述符类型知道如何将属性链接到一组方法。一个属性带有四个可选参数:fget,fset,fdel和doc。可以提供最后一个来定义链接到属性的文档字符串,就好像它是方法一样。这是一个Rectangle类的示例,可以通过直接访问存储两个角点的属性或使用width和height属性来控制它:

class Rectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2

    def _width_get(self):
        return self.x2 - self.x1

    def _width_set(self, value):
        self.x2 = self.x1 + value

    def _height_get(self):
        return self.y2 - self.y1

    def _height_set(self, value):
        self.y2 = self.y1 + value

    width = property(
        _width_get, _width_set,
        doc="rectangle width measured from left"
    )
    height = property(
        _height_get, _height_set,
        doc="rectangle height measured from top"
    )

    def __repr__(self):
        return "{}({}, {}, {}, {})".format(
            self.__class__.__name__,
            self.x1, self.y1, self.x2, self.y2
    )

创建属性的最佳语法是使用属性作为装饰器。这将减少类内部方法签名的数量,并使代码更具可读性和可维护性。使用装饰器,以上类变为:

class Rectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2

    @property
    def width(self):
        """rectangle height measured from top"""
        return self.x2 - self.x1

    @width.setter
    def width(self, value):
        self.x2 = self.x1 + value

    @property
    def height(self):
        """rectangle height measured from top"""
        return self.y2 - self.y1

    @height.setter
    def height(self, value):
        self.y2 = self.y1 + value

Decorators were added in Python to make function and method wrapping (a function that receives a function and returns an enhanced one) easier to read and understand. The original use case was to be able to define the methods as class methods or static methods on the head of their definition. Without the decorator syntax, it would require a rather sparse and repetitive definition:

class WithoutDecorators:
def some_static_method():
    print("this is static method")
some_static_method = staticmethod(some_static_method)

def some_class_method(cls):
    print("this is class method")
some_class_method = classmethod(some_class_method)

If the decorator syntax is used for the same purpose, the code is shorter and easier to understand:

class WithDecorators:
    @staticmethod
    def some_static_method():
        print("this is static method")

    @classmethod
    def some_class_method(cls):
        print("this is class method")

General syntax and possible implementations

The decorator is generally a named object ( lambda expressions are not allowed) that accepts a single argument when called (it will be the decorated function) and returns another callable object. “Callable” is used here instead of “function” with premeditation. While decorators are often discussed in the scope of methods and functions, they are not limited to them. In fact, anything that is callable (any object that implements the _call__ method is considered callable), can be used as a decorator and often objects returned by them are not simple functions but more instances of more complex classes implementing their own __call_ method.

The decorator syntax is simply only a syntactic sugar. Consider the following decorator usage:

@some_decorator
def decorated_function():
    pass

This can always be replaced by an explicit decorator call and function reassignment:

def decorated_function():
    pass
decorated_function = some_decorator(decorated_function)

However, the latter is less readable and also very hard to understand if multiple decorators are used on a single function. Decorators can be used in multiple different ways as shown below:

As a function

There are many ways to write custom decorators, but the simplest way is to write a function that returns a subfunction that wraps the original function call.

The generic patterns is as follows:

def mydecorator(function):
    def wrapped(*args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result
    # return wrapper as a decorated function
    return wrapped

As a class

While decorators almost always can be implemented using functions, there are some situations when using user-defined classes is a better option. This is often true when the decorator needs complex parametrization or it depends on a specific state.

The generic pattern for a nonparametrized decorator as a class is as follows:

class DecoratorAsClass:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = self.function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result

Parametrizing decorators

In real code, there is often a need to use decorators that can be parametrized. When the function is used as a decorator, then the solution is simple—a second level of wrapping has to be used. Here is a simple example of the decorator that repeats the execution of a decorated function the specified number of times every time it is called:

def repeat(number=3):
"""Cause decorated function to be repeated a number of times.

Last value of original function call is returned as a result
:param number: number of repetitions, 3 if not specified
"""
def actual_decorator(function):
    def wrapper(*args, **kwargs):
        result = None
        for _ in range(number):
            result = function(*args, **kwargs)
        return result
    return wrapper
return actual_decorator

The decorator defined this way can accept parameters:

>>> @repeat(2)
... def foo():
...     print("foo")
...
>>> foo()
foo
foo

Note that even if the parametrized decorator has default values for its arguments, the parentheses after its name is required. The correct way to use the preceding decorator with default arguments is as follows:

>>> @repeat()
... def bar():
...     print("bar")
...
>>> bar()
bar
bar
bar

Finally lets see decorators with Properties.

Properties

The properties provide a built-in descriptor type that knows how to link an attribute to a set of methods. A property takes four optional arguments: fget , fset , fdel , and doc . The last one can be provided to define a docstring that is linked to the attribute as if it were a method. Here is an example of a Rectangle class that can be controlled either by direct access to attributes that store two corner points or by using the width , and height properties:

class Rectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2

    def _width_get(self):
        return self.x2 - self.x1

    def _width_set(self, value):
        self.x2 = self.x1 + value

    def _height_get(self):
        return self.y2 - self.y1

    def _height_set(self, value):
        self.y2 = self.y1 + value

    width = property(
        _width_get, _width_set,
        doc="rectangle width measured from left"
    )
    height = property(
        _height_get, _height_set,
        doc="rectangle height measured from top"
    )

    def __repr__(self):
        return "{}({}, {}, {}, {})".format(
            self.__class__.__name__,
            self.x1, self.y1, self.x2, self.y2
    )

The best syntax for creating properties is using property as a decorator. This will reduce the number of method signatures inside of the class and make code more readable and maintainable. With decorators the above class becomes:

class Rectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2

    @property
    def width(self):
        """rectangle height measured from top"""
        return self.x2 - self.x1

    @width.setter
    def width(self, value):
        self.x2 = self.x1 + value

    @property
    def height(self):
        """rectangle height measured from top"""
        return self.y2 - self.y1

    @height.setter
    def height(self, value):
        self.y2 = self.y1 + value

回答 9

用其他方式说出别人的想法:是的,它是一个装饰器。

在Python中,就像:

  1. 创建一个函数(在@调用之后)
  2. 调用另一个函数以对您创建的函数进行操作。这将返回一个新函数。您调用的函数是@的参数。
  3. 用返回的新函数替换定义的函数。

这可以用于各种有用的东西,因为功能是对象,而只是指令而已,因此成为可能。

To say what others have in a different way: yes, it is a decorator.

In Python, it’s like:

  1. Creating a function (follows under the @ call)
  2. Calling another function to operate on your created function. This returns a new function. The function that you call is the argument of the @.
  3. Replacing the function defined with the new function returned.

This can be used for all kinds of useful things, made possible because functions are objects and just necessary just instructions.


回答 10

@符号还用于访问plydata / pandas数据框查询中的变量pandas.DataFrame.query。例:

df = pandas.DataFrame({'foo': [1,2,15,17]})
y = 10
df >> query('foo > @y') # plydata
df.query('foo > @y') # pandas

@ symbol is also used to access variables inside a plydata / pandas dataframe query, pandas.DataFrame.query. Example:

df = pandas.DataFrame({'foo': [1,2,15,17]})
y = 10
df >> query('foo > @y') # plydata
df.query('foo > @y') # pandas

回答 11

它表明您正在使用装饰器。这是Bruce Eckel在2008年的例子

It indicates that you are using a decorator. Here is Bruce Eckel’s example from 2008.


@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中制作两个装饰器,以完成以下工作?

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


静态方法和类方法之间的区别

问题:静态方法和类方法之间的区别

@staticmethod修饰的功能和用修饰的功能有什么区别@classmethod

What is the difference between a function decorated with @staticmethod and one decorated with @classmethod?


回答 0

也许有点示例代码将有助于:发现其中的差别在调用签名fooclass_foo并且static_foo

class A(object):
    def foo(self, x):
        print "executing foo(%s, %s)" % (self, x)

    @classmethod
    def class_foo(cls, x):
        print "executing class_foo(%s, %s)" % (cls, x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)" % x    

a = A()

以下是对象实例调用方法的常用方法。对象实例,a作为第一个参数隐式传递。

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)

使用classmethods时,对象实例的类作为第一个参数而不是隐式传递self

a.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

您也可以class_foo使用该类进行呼叫。实际上,如果您将某些东西定义为类方法,则可能是因为您打算从类而不是从类实例调用它。A.foo(1)本来会引发TypeError,但A.class_foo(1)效果很好:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

人们发现类方法的一种用途是创建可继承的替代构造函数


使用staticmethods时self(对象实例)和 cls(类)都不会隐式传递为第一个参数。它们的行为类似于普通函数,不同之处在于您可以从实例或类中调用它们:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

静态方法用于对与类之间具有某种逻辑联系的函数进行分组。


foo只是一个函数,但是当您调用a.foo它时,不仅得到函数,还会得到函数的“部分应用”版本,其中对象实例a绑定为函数的第一个参数。foo期望有2个参数,而a.foo只期望有1个参数。

a势必到foo。这就是下面的术语“绑定”的含义:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

a.class_fooa不绑定class_foo,而是与类A绑定class_foo

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

在这里,使用静态方法,即使它是一种方法,也a.static_foo只是返回一个没有绑定参数的良好的’ole函数。static_foo期望有1个参数,也 a.static_foo期望有1个参数。

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

当然,当您static_foo使用类进行调用时,也会发生同样的事情A

print(A.static_foo)
# <function static_foo at 0xb7d479cc>

Maybe a bit of example code will help: Notice the difference in the call signatures of foo, class_foo and static_foo:

class A(object):
    def foo(self, x):
        print "executing foo(%s, %s)" % (self, x)

    @classmethod
    def class_foo(cls, x):
        print "executing class_foo(%s, %s)" % (cls, x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)" % x    

a = A()

Below is the usual way an object instance calls a method. The object instance, a, is implicitly passed as the first argument.

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)

With classmethods, the class of the object instance is implicitly passed as the first argument instead of self.

a.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

You can also call class_foo using the class. In fact, if you define something to be a classmethod, it is probably because you intend to call it from the class rather than from a class instance. A.foo(1) would have raised a TypeError, but A.class_foo(1) works just fine:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

One use people have found for class methods is to create inheritable alternative constructors.


With staticmethods, neither self (the object instance) nor cls (the class) is implicitly passed as the first argument. They behave like plain functions except that you can call them from an instance or the class:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

Staticmethods are used to group functions which have some logical connection with a class to the class.


foo is just a function, but when you call a.foo you don’t just get the function, you get a “partially applied” version of the function with the object instance a bound as the first argument to the function. foo expects 2 arguments, while a.foo only expects 1 argument.

a is bound to foo. That is what is meant by the term “bound” below:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

With a.class_foo, a is not bound to class_foo, rather the class A is bound to class_foo.

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

Here, with a staticmethod, even though it is a method, a.static_foo just returns a good ‘ole function with no arguments bound. static_foo expects 1 argument, and a.static_foo expects 1 argument too.

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

And of course the same thing happens when you call static_foo with the class A instead.

print(A.static_foo)
# <function static_foo at 0xb7d479cc>

回答 1

一个静态方法是一无所知,它被称为上类或实例的方法。它只是获取传递的参数,没有隐式的第一个参数。在Python中基本上没有用-您可以使用模块函数代替静态方法。

类方法,在另一方面,是获取传递的类,它被称为上,或该类的实例,它被称为上的,作为第一个参数的方法。当您希望该方法成为类的工厂时,这很有用:由于它获得了作为第一个参数调用的实际类,因此即使涉及子类,也始终可以实例化正确的类。例如dict.fromkeys(),观察在子类上调用时,类方法如何返回子类的实例:

>>> class DictSubclass(dict):
...     def __repr__(self):
...         return "DictSubclass"
... 
>>> dict.fromkeys("abc")
{'a': None, 'c': None, 'b': None}
>>> DictSubclass.fromkeys("abc")
DictSubclass
>>> 

A staticmethod is a method that knows nothing about the class or instance it was called on. It just gets the arguments that were passed, no implicit first argument. It is basically useless in Python — you can just use a module function instead of a staticmethod.

A classmethod, on the other hand, is a method that gets passed the class it was called on, or the class of the instance it was called on, as first argument. This is useful when you want the method to be a factory for the class: since it gets the actual class it was called on as first argument, you can always instantiate the right class, even when subclasses are involved. Observe for instance how dict.fromkeys(), a classmethod, returns an instance of the subclass when called on a subclass:

>>> class DictSubclass(dict):
...     def __repr__(self):
...         return "DictSubclass"
... 
>>> dict.fromkeys("abc")
{'a': None, 'c': None, 'b': None}
>>> DictSubclass.fromkeys("abc")
DictSubclass
>>> 

回答 2

基本上@classmethod使方法的第一个参数是从其调用的类(而不是类实例),@staticmethod它没有任何隐式参数。

Basically @classmethod makes a method whose first argument is the class it’s called from (rather than the class instance), @staticmethod does not have any implicit arguments.


回答 3

官方python文档:

@classmethod

类方法将类作为隐式第一个参数接收,就像实例方法接收实例一样。要声明类方法,请使用以下惯用法:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ... 

@classmethod表单是一个函数 装饰器 –有关详细信息,请参见函数定义中的函数定义描述。

可以在类(如C.f())或实例(如C().f())上调用它。该实例除其类外均被忽略。如果为派生类调用类方法,则派生类对象作为隐式第一个参数传递。

类方法不同于C ++或Java静态方法。如果需要这些,请参阅staticmethod()本节。

@staticmethod

静态方法不会收到隐式的第一个参数。要声明静态方法,请使用以下惯用法:

class C:
    @staticmethod
    def f(arg1, arg2, ...): ... 

@staticmethod表单是一个函数 装饰器 –有关详细信息,请参见函数定义中的函数定义描述。

可以在类(如C.f())或实例(如C().f())上调用它。该实例除其类外均被忽略。

Python中的静态方法类似于Java或C ++中的静态方法。有关更高级的概念,请参阅 classmethod()本节。

Official python docs:

@classmethod

A class method receives the class as implicit first argument, just like an instance method receives the instance. To declare a class method, use this idiom:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ... 

The @classmethod form is a function decorator – see the description of function definitions in Function definitions for details.

It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.

Class methods are different than C++ or Java static methods. If you want those, see staticmethod() in this section.

@staticmethod

A static method does not receive an implicit first argument. To declare a static method, use this idiom:

class C:
    @staticmethod
    def f(arg1, arg2, ...): ... 

The @staticmethod form is a function decorator – see the description of function definitions in Function definitions for details.

It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class.

Static methods in Python are similar to those found in Java or C++. For a more advanced concept, see classmethod() in this section.


回答 4

是关于这个问题的简短文章

@staticmethod函数不过是在类内部定义的函数。可调用而无需先实例化该类。它的定义通过继承是不可变的。

@classmethod函数也可以在不实例化类的情况下调用,但是其定义是通过继承遵循Sub类,而不是Parent类。这是因为@classmethod函数的第一个参数必须始终为cls(类)。

Here is a short article on this question

@staticmethod function is nothing more than a function defined inside a class. It is callable without instantiating the class first. It’s definition is immutable via inheritance.

@classmethod function also callable without instantiating the class, but its definition follows Sub class, not Parent class, via inheritance. That’s because the first argument for @classmethod function must always be cls (class).


回答 5

要决定使用@staticmethod还是@classmethod,您必须查看方法内部。如果您的方法访问类中的其他变量/方法,请使用@classmethod。另一方面,如果您的方法未触及类的其他任何部分,请使用@staticmethod。

class Apple:

    _counter = 0

    @staticmethod
    def about_apple():
        print('Apple is good for you.')

        # note you can still access other member of the class
        # but you have to use the class instance 
        # which is not very nice, because you have repeat yourself
        # 
        # For example:
        # @staticmethod
        #    print('Number of apples have been juiced: %s' % Apple._counter)
        #
        # @classmethod
        #    print('Number of apples have been juiced: %s' % cls._counter)
        #
        #    @classmethod is especially useful when you move your function to other class,
        #       you don't have to rename the class reference 

    @classmethod
    def make_apple_juice(cls, number_of_apples):
        print('Make juice:')
        for i in range(number_of_apples):
            cls._juice_this(i)

    @classmethod
    def _juice_this(cls, apple):
        print('Juicing %d...' % apple)
        cls._counter += 1

To decide whether to use @staticmethod or @classmethod you have to look inside your method. If your method accesses other variables/methods in your class then use @classmethod. On the other hand, if your method does not touches any other parts of the class then use @staticmethod.

class Apple:

    _counter = 0

    @staticmethod
    def about_apple():
        print('Apple is good for you.')

        # note you can still access other member of the class
        # but you have to use the class instance 
        # which is not very nice, because you have repeat yourself
        # 
        # For example:
        # @staticmethod
        #    print('Number of apples have been juiced: %s' % Apple._counter)
        #
        # @classmethod
        #    print('Number of apples have been juiced: %s' % cls._counter)
        #
        #    @classmethod is especially useful when you move your function to other class,
        #       you don't have to rename the class reference 

    @classmethod
    def make_apple_juice(cls, number_of_apples):
        print('Make juice:')
        for i in range(number_of_apples):
            cls._juice_this(i)

    @classmethod
    def _juice_this(cls, apple):
        print('Juicing %d...' % apple)
        cls._counter += 1

回答 6

Python中的@staticmethod和@classmethod有什么区别?

您可能已经看到了类似此伪代码的Python代码,该代码演示了各种方法类型的签名,并提供了一个文档字符串来说明每种方法:

class Foo(object):

    def a_normal_instance_method(self, arg_1, kwarg_2=None):
        '''
        Return a value that is a function of the instance with its
        attributes, and other arguments such as arg_1 and kwarg2
        '''

    @staticmethod
    def a_static_method(arg_0):
        '''
        Return a value that is a function of arg_0. It does not know the 
        instance or class it is called from.
        '''

    @classmethod
    def a_class_method(cls, arg1):
        '''
        Return a value that is a function of the class and other arguments.
        respects subclassing, it is called with the class it is called from.
        '''

普通实例方法

首先,我会解释a_normal_instance_method。这就是所谓的“ 实例方法 ”。使用实例方法时,它用作部分函数(与总函数相反,在源代码中查看时为所有值定义的总函数),即在使用时,将第一个参数预定义为具有所有给定属性的对象。它具有绑定到其对象的实例,并且必须从该对象的实例调用它。通常,它将访问实例的各种属性。

例如,这是一个字符串的实例:

', '

如果我们join在该字符串上使用实例方法来连接另一个可迭代对象,则很明显,它是实例的功能,除了是可迭代列表的功能之外,还['a', 'b', 'c']

>>> ', '.join(['a', 'b', 'c'])
'a, b, c'

绑定方法

可以通过点分查找来绑定实例方法,以备后用。

例如,这将str.join方法绑定到':'实例:

>>> join_with_colons = ':'.join 

之后,我们可以将其用作已绑定第一个参数的函数。这样,它就像实例上的部分函数一样工作:

>>> join_with_colons('abcde')
'a:b:c:d:e'
>>> join_with_colons(['FF', 'FF', 'FF', 'FF', 'FF', 'FF'])
'FF:FF:FF:FF:FF:FF'

静态方法

静态方法并没有把实例作为参数。

它与模块级功能非常相似。

但是,模块级功能必须存在于模块中,并且必须专门导入到其他使用该功能的地方。

但是,如果将其附加到对象上,它也将通过导入和继承方便地跟随对象。

静态方法的一个示例是str.maketransstringPython 3 的模块中移出的。它使转换表适合由占用str.translate。从字符串的实例使用时,看起来确实很愚蠢,如下所示,但是从string模块导入函数相当笨拙,并且能够从类中调用它很好,例如str.maketrans

# demonstrate same function whether called from instance or not:
>>> ', '.maketrans('ABC', 'abc')
{65: 97, 66: 98, 67: 99}
>>> str.maketrans('ABC', 'abc')
{65: 97, 66: 98, 67: 99}

在python 2中,您必须从越来越少用的字符串模块中导入此函数:

>>> import string
>>> 'ABCDEFG'.translate(string.maketrans('ABC', 'abc'))
'abcDEFG'

类方法

类方法与实例方法类似,因为它采用了隐式的第一个参数,但是它采用了类,而不是采用实例。通常,它们被用作替代构造函数以更好地使用语义,并且它将支持继承。

内建类方法的最典型示例是dict.fromkeys。它用作dict的替代构造函数(非常适合当您知道键是什么并且想要它们的默认值时)。

>>> dict.fromkeys(['a', 'b', 'c'])
{'c': None, 'b': None, 'a': None}

当我们对dict进行子类化时,可以使用相同的构造函数,该构造函数创建子类的实例。

>>> class MyDict(dict): 'A dict subclass, use to demo classmethods'
>>> md = MyDict.fromkeys(['a', 'b', 'c'])
>>> md
{'a': None, 'c': None, 'b': None}
>>> type(md)
<class '__main__.MyDict'>

看到熊猫的源代码的替代构造其它类似的例子,同时也看到了官方的Python文档classmethodstaticmethod

What is the difference between @staticmethod and @classmethod in Python?

You may have seen Python code like this pseudocode, which demonstrates the signatures of the various method types and provides a docstring to explain each:

class Foo(object):

    def a_normal_instance_method(self, arg_1, kwarg_2=None):
        '''
        Return a value that is a function of the instance with its
        attributes, and other arguments such as arg_1 and kwarg2
        '''

    @staticmethod
    def a_static_method(arg_0):
        '''
        Return a value that is a function of arg_0. It does not know the 
        instance or class it is called from.
        '''

    @classmethod
    def a_class_method(cls, arg1):
        '''
        Return a value that is a function of the class and other arguments.
        respects subclassing, it is called with the class it is called from.
        '''

The Normal Instance Method

First I’ll explain a_normal_instance_method. This is precisely called an “instance method“. When an instance method is used, it is used as a partial function (as opposed to a total function, defined for all values when viewed in source code) that is, when used, the first of the arguments is predefined as the instance of the object, with all of its given attributes. It has the instance of the object bound to it, and it must be called from an instance of the object. Typically, it will access various attributes of the instance.

For example, this is an instance of a string:

', '

if we use the instance method, join on this string, to join another iterable, it quite obviously is a function of the instance, in addition to being a function of the iterable list, ['a', 'b', 'c']:

>>> ', '.join(['a', 'b', 'c'])
'a, b, c'

Bound methods

Instance methods can be bound via a dotted lookup for use later.

For example, this binds the str.join method to the ':' instance:

>>> join_with_colons = ':'.join 

And later we can use this as a function that already has the first argument bound to it. In this way, it works like a partial function on the instance:

>>> join_with_colons('abcde')
'a:b:c:d:e'
>>> join_with_colons(['FF', 'FF', 'FF', 'FF', 'FF', 'FF'])
'FF:FF:FF:FF:FF:FF'

Static Method

The static method does not take the instance as an argument.

It is very similar to a module level function.

However, a module level function must live in the module and be specially imported to other places where it is used.

If it is attached to the object, however, it will follow the object conveniently through importing and inheritance as well.

An example of a static method is str.maketrans, moved from the string module in Python 3. It makes a translation table suitable for consumption by str.translate. It does seem rather silly when used from an instance of a string, as demonstrated below, but importing the function from the string module is rather clumsy, and it’s nice to be able to call it from the class, as in str.maketrans

# demonstrate same function whether called from instance or not:
>>> ', '.maketrans('ABC', 'abc')
{65: 97, 66: 98, 67: 99}
>>> str.maketrans('ABC', 'abc')
{65: 97, 66: 98, 67: 99}

In python 2, you have to import this function from the increasingly less useful string module:

>>> import string
>>> 'ABCDEFG'.translate(string.maketrans('ABC', 'abc'))
'abcDEFG'

Class Method

A class method is a similar to an instance method in that it takes an implicit first argument, but instead of taking the instance, it takes the class. Frequently these are used as alternative constructors for better semantic usage and it will support inheritance.

The most canonical example of a builtin classmethod is dict.fromkeys. It is used as an alternative constructor of dict, (well suited for when you know what your keys are and want a default value for them.)

>>> dict.fromkeys(['a', 'b', 'c'])
{'c': None, 'b': None, 'a': None}

When we subclass dict, we can use the same constructor, which creates an instance of the subclass.

>>> class MyDict(dict): 'A dict subclass, use to demo classmethods'
>>> md = MyDict.fromkeys(['a', 'b', 'c'])
>>> md
{'a': None, 'c': None, 'b': None}
>>> type(md)
<class '__main__.MyDict'>

See the pandas source code for other similar examples of alternative constructors, and see also the official Python documentation on classmethod and staticmethod.


回答 7

我开始使用C ++,Java和Python学习编程语言,所以这个问题也困扰着我,直到我理解了每种语言的简单用法。

类方法:与Java和C ++不同,Python没有构造函数重载。因此,可以使用实现此目的classmethod。以下示例将对此进行解释

让我们考虑,我们有一个Person类,它有两个参数first_name,并last_name与创建的实例Person

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

现在,如果您只需要使用一个名称创建一个类(仅使用一个名称)first_name,那么您将无法在Python中执行类似的操作。

当您尝试创建对象(实例)时,这将给您一个错误。

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

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

但是,您可以使用@classmethod以下方法实现相同的目的

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @classmethod
    def get_person(cls, first_name):
        return cls(first_name, "")

静态方法:这很简单,它不受实例或类的约束,您可以使用类名简单地调用它。

因此,假设在上面的示例中,您需要一个first_name不超过20个字符的验证,您只需执行此操作即可。

@staticmethod  
def validate_name(name):
    return len(name) <= 20

你可以简单地使用 class name

Person.validate_name("Gaurang Shah")

I started learning programming language with C++ and then Java and then Python and so this question bothered me a lot as well, until I understood the simple usage of each.

Class Method: Python unlike Java and C++ doesn’t have constructor overloading. And so to achieve this you could use classmethod. Following example will explain this

Let’s consider we have a Person class which takes two arguments first_name and last_name and creates the instance of Person.

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

Now, if the requirement comes where you need to create a class using a single name only, just a first_name, you can’t do something like this in Python.

This will give you an error when you will try to create an object (instance).

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

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

However, you could achieve the same thing using @classmethod as mentioned below

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @classmethod
    def get_person(cls, first_name):
        return cls(first_name, "")

Static Method: This is rather simple, it’s not bound to instance or class and you can simply call that using class name.

So let’s say in above example you need a validation that first_name should not exceed 20 characters, you can simply do this.

@staticmethod  
def validate_name(name):
    return len(name) <= 20

and you could simply call using class name

Person.validate_name("Gaurang Shah")

回答 8

我认为一个更好的问题是“何时使用@classmethod与@staticmethod?”

@classmethod允许您轻松访问与类定义关联的私有成员。这是完成单例或工厂类的一种好方法,该类控制已创建对象的实例数量。

@staticmethod可以提供少量的性能提升,但是我还没有看到在类中有效地使用静态方法,而该方法不能作为类外的独立函数来实现。

I think a better question is “When would you use @classmethod vs @staticmethod?”

@classmethod allows you easy access to private members that are associated to the class definition. this is a great way to do singletons, or factory classes that control the number of instances of the created objects exist.

@staticmethod provides marginal performance gains, but I have yet to see a productive use of a static method within a class that couldn’t be achieved as a standalone function outside the class.


回答 9

@decorators是在python 2.4中添加的。如果您使用的是python <2.4,则可以使用classmethod()和staticmethod()函数。

例如,如果您想创建一个工厂方法(一个函数根据得到的参数返回一个类的不同实现的实例),您可以执行以下操作:

class Cluster(object):

    def _is_cluster_for(cls, name):
        """
        see if this class is the cluster with this name
        this is a classmethod
        """ 
        return cls.__name__ == name
    _is_cluster_for = classmethod(_is_cluster_for)

    #static method
    def getCluster(name):
        """
        static factory method, should be in Cluster class
        returns a cluster object for the given name
        """
        for cls in Cluster.__subclasses__():
            if cls._is_cluster_for(name):
                return cls()
    getCluster = staticmethod(getCluster)

还要注意,这是使用类方法和静态方法的一个很好的例子。静态方法显然属于该类,因为它在内部使用类Cluster。类方法仅需要有关类的信息,而无需对象的实例。

_is_cluster_for方法设为类方法的另一个好处是,子类可以决定更改其实现,这可能是因为它非常通用并且可以处理多种类型的集群,因此仅检查类的名称是不够的。

@decorators were added in python 2.4 If you’re using python < 2.4 you can use the classmethod() and staticmethod() function.

For example, if you want to create a factory method (A function returning an instance of a different implementation of a class depending on what argument it gets) you can do something like:

class Cluster(object):

    def _is_cluster_for(cls, name):
        """
        see if this class is the cluster with this name
        this is a classmethod
        """ 
        return cls.__name__ == name
    _is_cluster_for = classmethod(_is_cluster_for)

    #static method
    def getCluster(name):
        """
        static factory method, should be in Cluster class
        returns a cluster object for the given name
        """
        for cls in Cluster.__subclasses__():
            if cls._is_cluster_for(name):
                return cls()
    getCluster = staticmethod(getCluster)

Also observe that this is a good example for using a classmethod and a static method, The static method clearly belongs to the class, since it uses the class Cluster internally. The classmethod only needs information about the class, and no instance of the object.

Another benefit of making the _is_cluster_for method a classmethod is so a subclass can decide to change it’s implementation, maybe because it is pretty generic and can handle more than one type of cluster, so just checking the name of the class would not be enough.


回答 10

静态方法:

  • 没有自变量的简单函数。
  • 处理类属性;不在实例属性上。
  • 可以通过类和实例调用。
  • 内置函数staticmethod()用于创建它们。

静态方法的好处:

  • 它在类范围内本地化函数名称
  • 它将功能代码移近使用位置
  • 与模块级函数相比,导入更方便,因为不必专门导入每种方法

    @staticmethod
    def some_static_method(*args, **kwds):
        pass

类方法:

  • 具有第一个参数作为类名的函数。
  • 可以通过类和实例调用。
  • 这些是使用classmethod内置函数创建的。

     @classmethod
     def some_class_method(cls, *args, **kwds):
         pass

Static Methods:

  • Simple functions with no self argument.
  • Work on class attributes; not on instance attributes.
  • Can be called through both class and instance.
  • The built-in function staticmethod()is used to create them.

Benefits of Static Methods:

  • It localizes the function name in the classscope
  • It moves the function code closer to where it is used
  • More convenient to import versus module-level functions since each method does not have to be specially imported

    @staticmethod
    def some_static_method(*args, **kwds):
        pass
    

Class Methods:

  • Functions that have first argument as classname.
  • Can be called through both class and instance.
  • These are created with classmethod in-built function.

     @classmethod
     def some_class_method(cls, *args, **kwds):
         pass
    

回答 11

@staticmethod只是禁用默认函数作为方法描述符。classmethod将函数包装在可调用的容器中,该容器将对拥有类的引用作为第一个参数传递:

>>> class C(object):
...  pass
... 
>>> def f():
...  pass
... 
>>> staticmethod(f).__get__(None, C)
<function f at 0x5c1cf0>
>>> classmethod(f).__get__(None, C)
<bound method type.f of <class '__main__.C'>>

实际上,classmethod它具有运行时开销,但可以访问拥有的类。另外,我建议使用元类并将类方法放在该元类上:

>>> class CMeta(type):
...  def foo(cls):
...   print cls
... 
>>> class C(object):
...  __metaclass__ = CMeta
... 
>>> C.foo()
<class '__main__.C'>

@staticmethod just disables the default function as method descriptor. classmethod wraps your function in a container callable that passes a reference to the owning class as first argument:

>>> class C(object):
...  pass
... 
>>> def f():
...  pass
... 
>>> staticmethod(f).__get__(None, C)
<function f at 0x5c1cf0>
>>> classmethod(f).__get__(None, C)
<bound method type.f of <class '__main__.C'>>

As a matter of fact, classmethod has a runtime overhead but makes it possible to access the owning class. Alternatively I recommend using a metaclass and putting the class methods on that metaclass:

>>> class CMeta(type):
...  def foo(cls):
...   print cls
... 
>>> class C(object):
...  __metaclass__ = CMeta
... 
>>> C.foo()
<class '__main__.C'>

回答 12

关于如何在Python中使用静态,类或抽象方法的权威指南是该主题的一个很好的链接,并总结如下。

@staticmethod函数不过是在类内部定义的函数。可调用而无需先实例化该类。它的定义通过继承是不可变的。

  • Python不必实例化对象的绑定方法。
  • 它简化了代码的可读性,并且不依赖于对象本身的状态。

@classmethod函数也可以在不实例化该类的情况下调用,但是其定义遵循子类,而不是父类,通过继承可以被子类覆盖。这是因为@classmethodfunction 的第一个参数必须始终为cls(类)。

  • 工厂方法,用于使用例如某种预处理为类创建实例。
  • 静态方法调用静态方法:如果将静态方法拆分为多个静态方法,则不应硬编码类名,而应使用类方法

The definitive guide on how to use static, class or abstract methods in Python is one good link for this topic, and summary it as following.

@staticmethod function is nothing more than a function defined inside a class. It is callable without instantiating the class first. It’s definition is immutable via inheritance.

  • Python does not have to instantiate a bound-method for object.
  • It eases the readability of the code, and it does not depend on the state of object itself;

@classmethod function also callable without instantiating the class, but its definition follows Sub class, not Parent class, via inheritance, can be overridden by subclass. That’s because the first argument for @classmethod function must always be cls (class).

  • Factory methods, that are used to create an instance for a class using for example some sort of pre-processing.
  • Static methods calling static methods: if you split a static methods in several static methods, you shouldn’t hard-code the class name but use class methods

回答 13

只有第一个参数不同

  • 普通方法:当前对象(如果自动作为(附加)第一个参数传递)
  • classmethod:当前对象的类作为(附加)fist参数自动传递
  • 静态方法:不会自动传递其他参数。您传递给该函数的就是所得到的。

更详细地…

普通方法

调用对象的方法时,它会自动获得一个额外的参数self作为其第一个参数。即方法

def f(self, x, y)

必须使用2个参数调用。self是自动传递的,它是对象本身

类方法

装饰方法时

@classmethod
def f(cls, x, y)

自动提供的参数不是 self,而是的类 self

静态方法

装饰方法时

@staticmethod
def f(x, y)

该方法根本没有任何自动参数。仅提供调用它的参数。

用法

  • classmethod 主要用于替代构造函数。
  • staticmethod不使用对象的状态。它可能是类外部的函数。它仅放在类中以将具有相似功能的功能分组(例如,类似于Java的Math类静态方法)
class Point
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def frompolar(cls, radius, angle):
        """The `cls` argument is the `Point` class itself"""
        return cls(radius * cos(angle), radius * sin(angle))

    @staticmethod
    def angle(x, y):
        """this could be outside the class, but we put it here 
just because we think it is logically related to the class."""
        return atan(y, x)


p1 = Point(3, 2)
p2 = Point.frompolar(3, pi/4)

angle = Point.angle(3, 2)

Only the first argument differs:

  • normal method: the current object if automatically passed as an (additional) first argument
  • classmethod: the class of the current object is automatically passed as an (additional) fist argument
  • staticmethod: no extra arguments are automatically passed. What you passed to the function is what you get.

In more detail…

normal method

When an object’s method is called, it is automatically given an extra argument self as its first argument. That is, method

def f(self, x, y)

must be called with 2 arguments. self is automatically passed, and it is the object itself.

class method

When the method is decorated

@classmethod
def f(cls, x, y)

the automatically provided argument is not self, but the class of self.

static method

When the method is decorated

@staticmethod
def f(x, y)

the method is not given any automatic argument at all. It is only given the parameters that it is called with.

usages

  • classmethod is mostly used for alternative constructors.
  • staticmethod does not use the state of the object. It could be a function external to a class. It only put inside the class for grouping functions with similar functionality (for example, like Java’s Math class static methods)
class Point
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def frompolar(cls, radius, angle):
        """The `cls` argument is the `Point` class itself"""
        return cls(radius * cos(angle), radius * sin(angle))

    @staticmethod
    def angle(x, y):
        """this could be outside the class, but we put it here 
just because we think it is logically related to the class."""
        return atan(y, x)


p1 = Point(3, 2)
p2 = Point.frompolar(3, pi/4)

angle = Point.angle(3, 2)


回答 14

让我先说一下用@classmethod装饰的方法与@staticmethod装饰的方法之间的相似性。

相似:两者都可以在本身上调用,而不仅仅是类的实例。因此,从某种意义上来说,它们都是Class的方法

区别:类方法将接收类本身作为第一个参数,而静态方法则不接收。

因此,从某种意义上说,静态方法并不绑定于Class本身,而只是因为它可能具有相关的功能而挂在这里。

>>> class Klaus:
        @classmethod
        def classmthd(*args):
            return args

        @staticmethod
        def staticmthd(*args):
            return args

# 1. Call classmethod without any arg
>>> Klaus.classmthd()  
(__main__.Klaus,)  # the class gets passed as the first argument

# 2. Call classmethod with 1 arg
>>> Klaus.classmthd('chumma')
(__main__.Klaus, 'chumma')

# 3. Call staticmethod without any arg
>>> Klaus.staticmthd()  
()

# 4. Call staticmethod with 1 arg
>>> Klaus.staticmthd('chumma')
('chumma',)

Let me tell the similarity between a method decorated with @classmethod vs @staticmethod first.

Similarity: Both of them can be called on the Class itself, rather than just the instance of the class. So, both of them in a sense are Class’s methods.

Difference: A classmethod will receive the class itself as the first argument, while a staticmethod does not.

So a static method is, in a sense, not bound to the Class itself and is just hanging in there just because it may have a related functionality.

>>> class Klaus:
        @classmethod
        def classmthd(*args):
            return args

        @staticmethod
        def staticmthd(*args):
            return args

# 1. Call classmethod without any arg
>>> Klaus.classmthd()  
(__main__.Klaus,)  # the class gets passed as the first argument

# 2. Call classmethod with 1 arg
>>> Klaus.classmthd('chumma')
(__main__.Klaus, 'chumma')

# 3. Call staticmethod without any arg
>>> Klaus.staticmthd()  
()

# 4. Call staticmethod with 1 arg
>>> Klaus.staticmthd('chumma')
('chumma',)

回答 15

关于静态方法与类方法的另一个考虑是继承。假设您有以下类:

class Foo(object):
    @staticmethod
    def bar():
        return "In Foo"

然后,您想覆盖bar()一个子类:

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"

这是可行的,但是请注意,现在bar()子类(Foo2)中的实现不再可以利用该类的任何特定优势。例如,假设Foo2有一个magic()要在Foo2实现中使用的名为的方法bar()

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"
    @staticmethod
    def magic():
        return "Something useful you'd like to use in bar, but now can't" 

这里的解决办法是打电话Foo2.magic()bar(),但此时你重复自己(如果名称Foo2的改变,你必须记住要更新bar()方法)。

对我来说,这有点违反开放式/封闭式原则,因为做出的决定Foo会影响您在派生类中重构通用代码的能力(即扩展性较小)。如果bar()是a,classmethod我们会没事的:

class Foo(object):
    @classmethod
    def bar(cls):
        return "In Foo"

class Foo2(Foo):
    @classmethod
    def bar(cls):
        return "In Foo2 " + cls.magic()
    @classmethod
    def magic(cls):
        return "MAGIC"

print Foo2().bar()

给出: In Foo2 MAGIC

Another consideration with respect to staticmethod vs classmethod comes up with inheritance. Say you have the following class:

class Foo(object):
    @staticmethod
    def bar():
        return "In Foo"

And you then want to override bar() in a child class:

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"

This works, but note that now the bar() implementation in the child class (Foo2) can no longer take advantage of anything specific to that class. For example, say Foo2 had a method called magic() that you want to use in the Foo2 implementation of bar():

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"
    @staticmethod
    def magic():
        return "Something useful you'd like to use in bar, but now can't" 

The workaround here would be to call Foo2.magic() in bar(), but then you’re repeating yourself (if the name of Foo2 changes, you’ll have to remember to update that bar() method).

To me, this is a slight violation of the open/closed principle, since a decision made in Foo is impacting your ability to refactor common code in a derived class (ie it’s less open to extension). If bar() were a classmethod we’d be fine:

class Foo(object):
    @classmethod
    def bar(cls):
        return "In Foo"

class Foo2(Foo):
    @classmethod
    def bar(cls):
        return "In Foo2 " + cls.magic()
    @classmethod
    def magic(cls):
        return "MAGIC"

print Foo2().bar()

Gives: In Foo2 MAGIC


回答 16

我将尝试通过一个示例来说明基本区别。

class A(object):
    x = 0

    def say_hi(self):
        pass

    @staticmethod
    def say_hi_static():
        pass

    @classmethod
    def say_hi_class(cls):
        pass

    def run_self(self):
        self.x += 1
        print self.x # outputs 1
        self.say_hi()
        self.say_hi_static()
        self.say_hi_class()

    @staticmethod
    def run_static():
        print A.x  # outputs 0
        # A.say_hi() #  wrong
        A.say_hi_static()
        A.say_hi_class()

    @classmethod
    def run_class(cls):
        print cls.x # outputs 0
        # cls.say_hi() #  wrong
        cls.say_hi_static()
        cls.say_hi_class()

1-我们可以直接调用静态方法和类方法而无需初始化

# A.run_self() #  wrong
A.run_static()
A.run_class()

2-静态方法不能调用self方法,但可以调用其他static和classmethod

3-静态方法属于类,根本不会使用对象。

4-类方法不绑定到对象而是绑定到类。

I will try to explain the basic difference using an example.

class A(object):
    x = 0

    def say_hi(self):
        pass

    @staticmethod
    def say_hi_static():
        pass

    @classmethod
    def say_hi_class(cls):
        pass

    def run_self(self):
        self.x += 1
        print self.x # outputs 1
        self.say_hi()
        self.say_hi_static()
        self.say_hi_class()

    @staticmethod
    def run_static():
        print A.x  # outputs 0
        # A.say_hi() #  wrong
        A.say_hi_static()
        A.say_hi_class()

    @classmethod
    def run_class(cls):
        print cls.x # outputs 0
        # cls.say_hi() #  wrong
        cls.say_hi_static()
        cls.say_hi_class()

1 – we can directly call static and classmethods without initializing

# A.run_self() #  wrong
A.run_static()
A.run_class()

2- Static method cannot call self method but can call other static and classmethod

3- Static method belong to class and will not use object at all.

4- Class method are not bound to an object but to a class.


回答 17

@classmethod:可用于创建对该类创建的所有实例的共享全局访问……例如由多个用户更新记录….我特别发现创建单例时它也很有效..: )

@static方法:与与…相关联的类或实例无关,但出于可读性考虑,可以使用static方法

@classmethod : can be used to create a shared global access to all the instances created of that class….. like updating a record by multiple users…. I particulary found it use ful when creating singletons as well..:)

@static method: has nothing to do with the class or instance being associated with …but for readability can use static method


回答 18

您可能需要考虑以下两者之间的区别:

Class A:
    def foo():  # no self parameter, no decorator
        pass

Class B:
    @staticmethod
    def foo():  # no self parameter
        pass

这在python2和python3之间发生了变化:

python2:

>>> A.foo()
TypeError
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()

python3:

>>> A.foo()
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()

因此@staticmethod,仅在类中直接使用 for方法已成为python3中的可选方法。如果要从类和实例中调用它们,则仍需要使用@staticmethod装饰器。

unutbus的答案很好地涵盖了其他情况。

You might want to consider the difference between:

Class A:
    def foo():  # no self parameter, no decorator
        pass

and

Class B:
    @staticmethod
    def foo():  # no self parameter
        pass

This has changed between python2 and python3:

python2:

>>> A.foo()
TypeError
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()

python3:

>>> A.foo()
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()

So using @staticmethod for methods only called directly from the class has become optional in python3. If you want to call them from both class and instance, you still need to use the @staticmethod decorator.

The other cases have been well covered by unutbus answer.


回答 19

我的贡献演示之间的差异@classmethod@staticmethod以及实例方法,包括如何实例可以间接调用@staticmethod。但是@staticmethod与其从实例中间接调用a ,不如将其设为私有可能更像是“ pythonic”。这里没有演示从私有方法获取某些东西,但是基本上是相同的概念。

#!python3

from os import system
system('cls')
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

class DemoClass(object):
    # instance methods need a class instance and
    # can access the instance through 'self'
    def instance_method_1(self):
        return 'called from inside the instance_method_1()'

    def instance_method_2(self):
        # an instance outside the class indirectly calls the static_method
        return self.static_method() + ' via instance_method_2()'

    # class methods don't need a class instance, they can't access the
    # instance (self) but they have access to the class itself via 'cls'
    @classmethod
    def class_method(cls):
        return 'called from inside the class_method()'

    # static methods don't have access to 'cls' or 'self', they work like
    # regular functions but belong to the class' namespace
    @staticmethod
    def static_method():
        return 'called from inside the static_method()'
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# works even if the class hasn't been instantiated
print(DemoClass.class_method() + '\n')
''' called from inside the class_method() '''

# works even if the class hasn't been instantiated
print(DemoClass.static_method() + '\n')
''' called from inside the static_method() '''
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# >>>>> all methods types can be called on a class instance <<<<<
# instantiate the class
democlassObj = DemoClass()

# call instance_method_1()
print(democlassObj.instance_method_1() + '\n')
''' called from inside the instance_method_1() '''

# # indirectly call static_method through instance_method_2(), there's really no use
# for this since a @staticmethod can be called whether the class has been
# instantiated or not
print(democlassObj.instance_method_2() + '\n')
''' called from inside the static_method() via instance_method_2() '''

# call class_method()
print(democlassObj.class_method() + '\n')
'''  called from inside the class_method() '''

# call static_method()
print(democlassObj.static_method())
''' called from inside the static_method() '''

"""
# whether the class is instantiated or not, this doesn't work
print(DemoClass.instance_method_1() + '\n')
'''
TypeError: TypeError: unbound method instancemethod() must be called with
DemoClass instance as first argument (got nothing instead)
'''
"""

My contribution demonstrates the difference amongst @classmethod, @staticmethod, and instance methods, including how an instance can indirectly call a @staticmethod. But instead of indirectly calling a @staticmethod from an instance, making it private may be more “pythonic.” Getting something from a private method isn’t demonstrated here but it’s basically the same concept.

#!python3

from os import system
system('cls')
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

class DemoClass(object):
    # instance methods need a class instance and
    # can access the instance through 'self'
    def instance_method_1(self):
        return 'called from inside the instance_method_1()'

    def instance_method_2(self):
        # an instance outside the class indirectly calls the static_method
        return self.static_method() + ' via instance_method_2()'

    # class methods don't need a class instance, they can't access the
    # instance (self) but they have access to the class itself via 'cls'
    @classmethod
    def class_method(cls):
        return 'called from inside the class_method()'

    # static methods don't have access to 'cls' or 'self', they work like
    # regular functions but belong to the class' namespace
    @staticmethod
    def static_method():
        return 'called from inside the static_method()'
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# works even if the class hasn't been instantiated
print(DemoClass.class_method() + '\n')
''' called from inside the class_method() '''

# works even if the class hasn't been instantiated
print(DemoClass.static_method() + '\n')
''' called from inside the static_method() '''
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# >>>>> all methods types can be called on a class instance <<<<<
# instantiate the class
democlassObj = DemoClass()

# call instance_method_1()
print(democlassObj.instance_method_1() + '\n')
''' called from inside the instance_method_1() '''

# # indirectly call static_method through instance_method_2(), there's really no use
# for this since a @staticmethod can be called whether the class has been
# instantiated or not
print(democlassObj.instance_method_2() + '\n')
''' called from inside the static_method() via instance_method_2() '''

# call class_method()
print(democlassObj.class_method() + '\n')
'''  called from inside the class_method() '''

# call static_method()
print(democlassObj.static_method())
''' called from inside the static_method() '''

"""
# whether the class is instantiated or not, this doesn't work
print(DemoClass.instance_method_1() + '\n')
'''
TypeError: TypeError: unbound method instancemethod() must be called with
DemoClass instance as first argument (got nothing instead)
'''
"""

回答 20

类方法将类作为隐式第一个参数接收,就像实例方法接收实例一样。它是绑定到类而不是类对象的方法,因为它使用指向类而不是对象实例的类参数,所以可以访问类的状态。它可以修改适用于该类所有实例的类状态。例如,它可以修改将适用于所有实例的类变量。

另一方面,与类方法或实例方法相比,静态方法不接收隐式的第一个参数。并且无法访问或修改类状态。它仅属于该类,因为从设计的角度来看这是正确的方法。但是就功能而言,在运行时未绑定到该类。

作为准则,请将静态方法用作实用程序,将类方法用作例如factory。或定义一个单例。并使用实例方法对实例的状态和行为进行建模。

希望我很清楚!

A class method receives the class as implicit first argument, just like an instance method receives the instance. It is a method which is bound to the class and not the object of the class.It has access to the state of the class as it takes a class parameter that points to the class and not the object instance. It can modify a class state that would apply across all the instances of the class. For example it can modify a class variable that will be applicable to all the instances.

On the other hand, a static method does not receive an implicit first argument, compared to class methods or instance methods. And can’t access or modify class state. It only belongs to the class because from design point of view that is the correct way. But in terms of functionality is not bound, at runtime, to the class.

as a guideline, use static methods as utilities, use class methods for example as factory . Or maybe to define a singleton. And use instance methods to model the state and behavior of instances.

Hope I was clear !


回答 21

顾名思义,类方法用于更改类而不是对象。为了更改类,他们将修改类属性(而不是对象属性),因为这是更新类的方式。这就是类方法将类(通常用“ cls”表示)作为第一个参数的原因。

class A(object):
    m=54

    @classmethod
    def class_method(cls):
        print "m is %d" % cls.m

另一方面,静态方法用于执行未绑定到类的功能,即它们不会读取或写入类变量。因此,静态方法不将类作为参数。使用它们是为了使类执行与该类目的不直接相关的功能。

class X(object):
    m=54 #will not be referenced

    @staticmethod
    def static_method():
        print "Referencing/calling a variable or function outside this class. E.g. Some global variable/function."

Class methods, as the name suggests, are used to make changes to classes and not the objects. To make changes to classes, they will modify the class attributes(not object attributes), since that is how you update classes. This is the reason that class methods take the class(conventionally denoted by ‘cls’) as the first argument.

class A(object):
    m=54

    @classmethod
    def class_method(cls):
        print "m is %d" % cls.m

Static methods on the other hand, are used to perform functionalities that are not bound to the class i.e. they will not read or write class variables. Hence, static methods do not take classes as arguments. They are used so that classes can perform functionalities that are not directly related to the purpose of the class.

class X(object):
    m=54 #will not be referenced

    @staticmethod
    def static_method():
        print "Referencing/calling a variable or function outside this class. E.g. Some global variable/function."

回答 22

从字面上分析@staticmethod可以提供不同的见解。

类的常规方法是隐式动态方法,该方法将实例作为第一个参数。
相反,静态方法不将实例作为第一个参数,因此称为“静态”

静态方法确实是一种正常的功能,与类定义之外的功能相同。
幸运的是,将它分组在类中只是为了靠近它的应用位置,或者您可以滚动查找它。

Analyze @staticmethod literally providing different insights.

A normal method of a class is an implicit dynamic method which takes the instance as first argument.
In contrast, a staticmethod does not take the instance as first argument, so is called ‘static’.

A staticmethod is indeed such a normal function the same as those outside a class definition.
It is luckily grouped into the class just in order to stand closer where it is applied, or you might scroll around to find it.


回答 23

我认为给出一个纯Python版本的staticmethodclassmethod将有助于在语言级别上理解它们之间的区别。

它们都是非数据描述符(如果您先熟悉描述符,会更容易理解它们)。

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

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

    def __get__(self, obj, objtype=None):
        return self.f


class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        def inner(*args, **kwargs):
            if cls is None:
                cls = type(obj)
            return self.f(cls, *args, **kwargs)
        return inner

I think giving a purely Python version of staticmethod and classmethod would help to understand the difference between them at language level.

Both of them are non-data descriptors (It would be easier to understand them if you are familiar with descriptors first).

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

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

    def __get__(self, obj, objtype=None):
        return self.f


class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        def inner(*args, **kwargs):
            if cls is None:
                cls = type(obj)
            return self.f(cls, *args, **kwargs)
        return inner

回答 24

静态方法无法访问继承层次结构中的对象,类或父类的服装。可以直接在类上调用它(无需创建对象)。

classmethod无法访问该对象的属性。但是,它可以访问继承层次结构中的类和父类的属性。可以直接在类上调用它(无需创建对象)。如果在该对象上调用,则它与普通方法相同,后者不会访问self.<attribute(s)>并且self.__class__.<attribute(s)>只能访问。

认为我们有一个带有的类b=2,我们将创建一个对象并将其重新设置为b=4其中。静态方法无法访问以前的任何内容。Classmethod .b==2只能通过进行访问cls.b。:普通方法可以同时访问.b==4通过self.b.b==2通过self.__class__.b

我们可以遵循KISS风格(保持简单,愚蠢):不要使用静态方法和类方法,不要在未实例化它们的情况下使用类,仅访问对象的属性self.attribute(s)。在某些语言中,以这种方式实现了OOP,我认为这不是一个坏主意。:)

staticmethod has no access to attibutes of the object, of the class, or of parent classes in the inheritance hierarchy. It can be called at the class directly (without creating an object).

classmethod has no access to attributes of the object. It however can access attributes of the class and of parent classes in the inheritance hierarchy. It can be called at the class directly (without creating an object). If called at the object then it is the same as normal method which doesn’t access self.<attribute(s)> and accesses self.__class__.<attribute(s)> only.

Think we have a class with b=2, we will create an object and re-set this to b=4 in it. Staticmethod cannot access nothing from previous. Classmethod can access .b==2 only, via cls.b. Normal method can access both: .b==4 via self.b and .b==2 via self.__class__.b.

We could follow the KISS style (keep it simple, stupid): Don’t use staticmethods and classmethods, don’t use classes without instantiating them, access only the object’s attributes self.attribute(s). There are languages where the OOP is implemented that way and I think it is not bad idea. :)


回答 25

在iPython中对其他相同方法的快速分析表明,该方法会@staticmethod产生少量的性能提升(以纳秒为单位),但否则似乎无济于事。另外,staticmethod()在编译过程中(通过运行脚本执行任何代码之前),通过处理该方法的其他工作可能会消除所有性能提升。

出于代码可读性的考虑,@staticmethod除非您的方法用于纳秒级的工作负载,否则我将避免使用。

A quick hack-up ofotherwise identical methods in iPython reveals that @staticmethod yields marginal performance gains (in the nanoseconds), but otherwise it seems to serve no function. Also, any performance gains will probably be wiped out by the additional work of processing the method through staticmethod() during compilation (which happens prior to any code execution when you run a script).

For the sake of code readability I’d avoid @staticmethod unless your method will be used for loads of work, where the nanoseconds count.