我想了解内置函数属性是如何工作的。让我困惑的是,属性也可以用作装饰器,但它只在用作内置函数时接受参数,而在用作装饰器时不接受参数。

下面的例子来自文档:

class C:
    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.")

属性的参数是getx, setx, delx和一个doc字符串。

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

class C:
    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.setter和x.deleter装饰器?


当前回答

下面是另一个例子:

##
## 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(对象)的例子相同,除了我使用x代替…我也没有在__init中初始化-…嗯. .我有,但它可以被删除,因为__x被定义为类....的一部分

输出结果为:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

如果我注释掉自我。在init中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语句也是如此……

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

注意:在这个论坛的代码输出是坏的-我必须在代码的第一部分添加空格,以使其工作-当复制/粘贴时,确保您将所有空格转换为制表符....我在Python中使用制表符,因为在一个10,000行的文件中,空格的文件大小可以是512KB到1MB,制表符的文件大小可以是100到200KB,这相当于文件大小的巨大差异,并减少了处理时间……

标签也可以调整每个用户-所以如果你喜欢2个空间宽度,4,8或任何你可以做的,这意味着它是周到的开发人员与视力缺陷。

注意:类中定义的所有函数都没有缩进,因为论坛软件中的一个bug -如果复制/粘贴,请确保缩进

其他回答

在下面,我给出了一个例子来阐明@property

考虑一个名为Student的类,它有两个变量:name和class_number,并且希望class_number在1到5的范围内。

现在我将解释两个错误的解决方案,最后是正确的解决方案:


下面的代码是错误的,因为它没有验证class_number(在1到5的范围内)

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = class_number

尽管经过验证,但这个解决方案也是错误的:

def validate_class_number(number):
    if 1 <= number <= 5:
        return number
    else:
        raise Exception("class number should be in the range of 1 to 5")

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = validate_class_number(class_number)

因为class_number验证只在创建类实例时检查,在创建类实例后不检查(可以将class_number更改为1到5范围之外的数字):

student1 = Student("masoud",5)
student1.class_number = 7

正确的解决方法是:

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = class_number
        
    @property
    def class_number(self):
        return self._class_number

    @class_number.setter
    def class_number(self, class_number):
        if not (1 <= class_number <= 5): raise Exception("class number should be in the range of 1 to 5")
        self._class_number = class_number

下面是另一个例子:

##
## 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(对象)的例子相同,除了我使用x代替…我也没有在__init中初始化-…嗯. .我有,但它可以被删除,因为__x被定义为类....的一部分

输出结果为:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

如果我注释掉自我。在init中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语句也是如此……

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

注意:在这个论坛的代码输出是坏的-我必须在代码的第一部分添加空格,以使其工作-当复制/粘贴时,确保您将所有空格转换为制表符....我在Python中使用制表符,因为在一个10,000行的文件中,空格的文件大小可以是512KB到1MB,制表符的文件大小可以是100到200KB,这相当于文件大小的巨大差异,并减少了处理时间……

标签也可以调整每个用户-所以如果你喜欢2个空间宽度,4,8或任何你可以做的,这意味着它是周到的开发人员与视力缺陷。

注意:类中定义的所有函数都没有缩进,因为论坛软件中的一个bug -如果复制/粘贴,请确保缩进

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

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

等于

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

装饰器是一个函数,它接受一个函数作为参数并返回一个闭包。闭包是一组内部函数和自由变量。内部函数在自由变量上闭合,这就是为什么它被称为“闭包”。自由变量是内部函数外部的变量,通过修饰器传入内部函数。

顾名思义,decorator修饰接收到的函数。

function decorator(undecorated_func):
    print("calling decorator func")
    inner():
       print("I am inside inner")
       return undecorated_func
    return inner

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

   undecorated_function= decorator(undecorated_func) 

现在内部函数被称为“unorated_func”。由于inner现在被命名为“unorated_func”,我们将“unorated_func”传递给装饰器,并返回“unorated_func”,并打印出“I am inside inner”。因此这个打印语句修饰了我们的“unorated_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

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

name=property(name) # Person.__dict__ you ll see name 

property()的第一个参数是getter。这是第二次装饰的情况:

   name=name.setter(name) 

如上所述,装饰器返回内部函数,我们用传递的函数名命名内部函数。

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

  name=property(name)

在第二个例子中,我们得到了这个

  name=name.setter(name)

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

下面是@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类,去掉美元和美分属性,而是决定只跟踪美分的总数:

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类的人都必须更改所有使用美元和美分的代码行,这可能是非常痛苦的……那么,如何避免这种情况呢?通过使用@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;这里我举一个最小的例子。