@property装饰器如何工作?

ash*_*him 889 python properties decorator python-internals python-decorators

我想了解内置函数的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.")
Run Code Online (Sandbox Code Playgroud)

property的论点是getx,setx,delx和文档字符串.

在下面的代码中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
Run Code Online (Sandbox Code Playgroud)

而且,如何在x.setterx.deleter装饰创造出来的?我很迷惑.

Mar*_*ers 934

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

>>> property()
<property object at 0x10ff07940>
Run Code Online (Sandbox Code Playgroud)

这个对象有额外的方法:

>>> 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>
Run Code Online (Sandbox Code Playgroud)

这些作为装饰.他们返回一个新的属性对象:

>>> property().getter(None)
<property object at 0x10ff079f0>
Run Code Online (Sandbox Code Playgroud)

这是旧对象的副本,但替换了其中一个函数.

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

@property
def foo(self): return self._foo
Run Code Online (Sandbox Code Playgroud)

真的意味着同样的事情

def foo(self): return self._foo
foo = property(foo)
Run Code Online (Sandbox Code Playgroud)

所以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
Run Code Online (Sandbox Code Playgroud)

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

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Run Code Online (Sandbox Code Playgroud)

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

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的,property对象作为一个描述符对象,所以它有.__get__(),.__set__().__delete__()方法挂接到实例属性获取,设置和删除:

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

描述符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__)
Run Code Online (Sandbox Code Playgroud)

  • 方法对象是动态创建的,并且*可以*重用相同的内存位置(如果可用). (12认同)
  • 很好.你可以添加一个事实:在`Foo.prop = prop`之后你可以做`Foo().prop = 5; pront Foo().prop; 具有所需结果的del Foo().prop`. (10认同)
  • @MarkusMeskanen:参见[Python overriding getter without setter](/sf/answers/1105030461/); 如果`@ human.name.getter`就地改变了`property`对象而不是返回new,那么`human.name`属性会被改变,改变那个超类的行为. (5认同)
  • @MarkusMeskanen:我宁愿使用“type()”,因为访问 dunder 属性和方法旨在被标准函数和运算符用作扩展点。 (4认同)
  • @MartijnPieters 为什么我们每次调用 `getter()`、`setter()` 或 `deleter()` 时都会返回一个新的 `Property` 实例,而不是仅仅修改现有实例 (`self.fset = fset; return self `)? (2认同)
  • @MarkusMeskanen:因为对象是不可变的,如果你在原地进行了变异,你就无法将它专门化为子类. (2认同)

J0H*_*0HN 173

文档说它只是创建只读属性的快捷方式.所以

@property
def x(self):
    return self._x
Run Code Online (Sandbox Code Playgroud)

相当于

def getx(self):
    return self._x
x = property(getx)
Run Code Online (Sandbox Code Playgroud)

  • 完整的上下文(最受欢迎的答案)是好的,但是这个答案对于弄清楚为什么其他人在类中使用@property作为装饰器非常有用。 (11认同)
  • “...创建只读属性的快捷方式。”。百万美元的答案! (7认同)
  • @FedericoRazzoli:`obj._x = 5` 可以。`obj.x = 5` 不起作用(除非您在 Python 2 上使用不完全支持描述符的旧式类),因为没有定义 setter。属性本身是只读的(除非你尝试在类本身而不是类的实例上修改它),只是Python对只读*属性*没有明显的支持(你能做的最好的就是子类` tuple` 并使用 `tuple` 的值存储,命名属性提供更友好的访问;这就是 `collections.namedtuple`/`typing.NamedTuple` 为您所做的)。 (4认同)

Ale*_*exG 95

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

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

>>> print( Thing('ok').word )
'ok'
Run Code Online (Sandbox Code Playgroud)

否则word仍然是方法而不是属性.

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

>>> print( Thing('ok').word() )
'ok'
Run Code Online (Sandbox Code Playgroud)

  • 有人可以解释一下为什么我要在这里创建一个属性装饰器,而不是仅仅使用`self.word = my_word` -然后将以与`print(Thing('ok')。word)='ok'相同的方式工作。 (4认同)
  • 您能解释一下,打印 `Thing('ok').word` 如何在运行时内部调用该函数吗? (3认同)
  • 如果需要在 __init__ 中定义 word() 函数/属性,这个示例会是什么样子? (2认同)
  • @SilverSlash这只是一个简单的例子,真正的用例会涉及更复杂的方法 (2认同)

glg*_*lgl 79

第一部分很简单:

@property
def x(self): ...
Run Code Online (Sandbox Code Playgroud)

是相同的

def x(self): ...
x = property(x)
Run Code Online (Sandbox Code Playgroud)
  • 反过来,它是用于property仅使用getter 创建a的简化语法.

下一步是使用setter和deleter扩展此属性.这种情况发生在适当的方法:

@x.setter
def x(self, value): ...
Run Code Online (Sandbox Code Playgroud)

返回一个新属性,它继承了旧的x加上给定的setter的所有内容.

x.deleter 以同样的方式工作.


Bil*_*ore 44

以下内容:

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
Run Code Online (Sandbox Code Playgroud)

是相同的:

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.")
Run Code Online (Sandbox Code Playgroud)

是相同的:

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)
Run Code Online (Sandbox Code Playgroud)

是相同的:

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)
Run Code Online (Sandbox Code Playgroud)

这与以下相同:

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
Run Code Online (Sandbox Code Playgroud)

  • 第一个和最后一个代码示例是相同的(普通)。 (2认同)
  • 我认为这是故意的。不管怎样,这对我来说是最有用的例子,因为我可以从这些例子中理解含义。谢谢@比尔摩尔 (2认同)

Cle*_*leb 31

下面是另一个例子,说明如何@property重构从这里获取的代码(我只在下面总结):

想象一下,你创建了一个Money这样的类:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents
Run Code Online (Sandbox Code Playgroud)

并且用户根据他/她使用的类来创建库,例如

money = Money(27, 12)

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

现在让我们假设你决定改变你的Money类并摆脱dollarscents属性,而是决定只跟踪总分数:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents
Run Code Online (Sandbox Code Playgroud)

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

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
Run Code Online (Sandbox Code Playgroud)

它会导致错误

AttributeError:'Money'对象没有属性'dollar'

这意味着现在每个依赖原始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
Run Code Online (Sandbox Code Playgroud)

当我们现在从我们的图书馆打电话

money = Money(27, 12)

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

它将按预期工作,我们不必更改我们的库中的单行代码!事实上,我们甚至不必知道我们依赖的库已经改变了.

另外,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.
Run Code Online (Sandbox Code Playgroud)

  • @ShengBi:不要过多关注实际示例,而应更多关注基本原则:如果 - 无论出于何种原因 - 您必须重构代码,您可以这样做而不会影响任何其他人的代码。 (4认同)
  • @cleb,你是真正的 MVP。其他人都使用像这样的 getter setter 示例,https://www.programiz.com/python-programming/property。但你是唯一一个真正解释了我们为什么想要财产的人。这是因为,当我们编写很多人要构建的东西时,我们希望能够修改基类,而不会对后继者如何使用或构建我们的工作(实现方面)产生真正的影响。 (4认同)
  • 你的总结很好,网站拿的例子有点奇怪..初学者会问..为什么我们不能坚持`self.dollar = Dollar`?我们对@property 做了很多工作,但似乎没有添加提取功能。 (2认同)

Leo*_*nkv 17

我在这里阅读了所有帖子并意识到我们可能需要一个真实的例子,为什么,实际上,我们有@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)
Run Code Online (Sandbox Code Playgroud)

在这段代码中,我们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()
...
Run Code Online (Sandbox Code Playgroud)

password用户填写表单时来自注册表单的注意属性.密码确认发生在前端EqualTo('password', message='Passwords must match')(如果你想知道,但它是一个与Flask表格不同的主题).

我希望这个例子很有用


Dev*_*hat 13

许多人已经清除了这一点,但这是我正在寻找的直接观点.这是我觉得从@property装饰器开始很重要的.例如:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"
Run Code Online (Sandbox Code Playgroud)

调用函数"get_config()"将像这样工作.

util = UtilityMixin()
print(util.get_config)
Run Code Online (Sandbox Code Playgroud)

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


Div*_*wat 12

让我们从Python装饰器开始.

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
Run Code Online (Sandbox Code Playgroud)

在这里,我们可以说decorator函数修改了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()
Run Code Online (Sandbox Code Playgroud)

让我们结束一切,而不是一个案例场景,但在此之前让我们谈谈一些oops原则.

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

这些方法当然是用于检索数据的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
Run Code Online (Sandbox Code Playgroud)

重构代码,以下是我们如何通过属性实现它.

在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)
Run Code Online (Sandbox Code Playgroud)

这里,

temperature = property(get_temperature,set_temperature)
Run Code Online (Sandbox Code Playgroud)

可能已被打破,因为,

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Run Code Online (Sandbox Code Playgroud)

注意事项:

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

现在您可以通过写入来访问温度值.

C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Run Code Online (Sandbox Code Playgroud)

我们可以进一步继续并且不定义名称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
Run Code Online (Sandbox Code Playgroud)

注意事项 -

  1. 用于获取值的方法用"@property"修饰.
  2. 必须用作setter的方法用"@ 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)
Run Code Online (Sandbox Code Playgroud)

现在,让我们进一步假设我们的课程在客户中受欢迎,他们开始在他们的程序中使用它,他们对对象进行了各种任务.

在一个重要的日子里,一位值得信赖的客户来到我们这里,并建议"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
Run Code Online (Sandbox Code Playgroud)

这很好,不是吗:您可以从可以想象的最简单的实现开始,然后您可以自由地迁移到属性版本而无需更改界面!所以属性不仅仅是getter和setter的替代品!

您可以在此处查看此实施

  • 您的摄氏类将在设置时无限递归(这意味着在实例化时)。 (3认同)
  • 与投票最高的答案相比,这个答案是为人类设计的;谢谢。 (2认同)
  • 您应该将温度 getter 和 setter 方法从 self.Temperature 更改为 self._Temperature,否则它会递归运行。 (2认同)

pro*_*sti 6

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

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

print(property) #<class 'property'>
Run Code Online (Sandbox Code Playgroud)

我改写了示例,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)
Run Code Online (Sandbox Code Playgroud)

在功能上与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)
Run Code Online (Sandbox Code Playgroud)

如您所见,我们使用该属性的方式没有任何区别。

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


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

prop = property(g,s,d)
Run Code Online (Sandbox Code Playgroud)

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

prop = property(fget=g,fset=s,fdel=d)
Run Code Online (Sandbox Code Playgroud)

的含义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
Run Code Online (Sandbox Code Playgroud)

下图显示了来自类的三胞胎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"
Run Code Online (Sandbox Code Playgroud)


Vic*_*ang 6

最好的解释可以在这里找到:Python @Property Explained – How to Use and When? (完整示例)作者:Selva Prabhakaran | 发表于 2018 年 11 月 5 日

它帮助我理解为什么不仅仅是如何。

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


Yil*_*maz 5

装饰器是一个将函数作为参数并返回闭包的函数。闭包是一组内部函数和自由变量。内部函数关闭自由变量,这就是它被称为“闭包”的原因。自由变量是位于内部函数外部并通过 docorator 传递到内部函数的变量。

顾名思义,装饰器就是对接收到的函数进行装饰。

function decorator(undecorated_func):
    print("calling decorator func")
    inner():
       print("I am inside inner")
       return undecorated_func
    return inner
Run Code Online (Sandbox Code Playgroud)

这是一个简单的装饰器函数。它收到“undecoated_func”并将其作为自由变量传递给inner(),inner()打印“I am inside inside”并返回undecorated_func。当我们调用时decorator(undecorated_func),它返回inner. 这是关键,在装饰器中,我们将内部函数命名为我们传递的函数的名称。

   undecorated_function= decorator(undecorated_func) 
Run Code Online (Sandbox Code Playgroud)

现在内部函数被称为“undecorated_func”。由于inner现在被命名为“undecolated_func”,我们将“undecolated_func”传递给装饰器,然后返回“undecolated_func”并打印出“I am inside inside”。所以这个 print 语句装饰了我们的“undecorated_func”。

现在让我们定义一个带有属性装饰器的类:

class Person:
    def __init__(self,name):
        self._name=name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self.value):
        self._name=value
Run Code Online (Sandbox Code Playgroud)

当我们用 @property() 修饰 name() 时,发生了这样的事情:

name=property(name) # Person.__dict__ you ll see name 
Run Code Online (Sandbox Code Playgroud)

property() 的第一个参数是 getter。这是第二个装饰中发生的事情:

   name=name.setter(name) 
Run Code Online (Sandbox Code Playgroud)

正如我上面提到的,装饰器返回内部函数,我们用我们传递的函数的名称来命名内部函数。

这里有一件重要的事情需要注意。“名字”是不可变的。在第一个装饰中,我们得到了这个:

  name=property(name)
Run Code Online (Sandbox Code Playgroud)

在第二个中我们得到了这个

  name=name.setter(name)
Run Code Online (Sandbox Code Playgroud)

我们不会修改名称 obj。在第二个装饰中,python 看到这是属性对象并且它已经有 getter 了。因此 python 创建一个新的“name”对象,添加第一个 obj 中的“fget”,然后设置“fset”。