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

下面的例子来自文档:

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装饰器?


当前回答

在下面,我给出了一个例子来阐明@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 @属性解释-如何使用和何时?(例子) 作者:塞尔瓦·普拉巴卡兰|发表于2018年11月5日

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

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

下面是另一个例子:

##
## 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如何在重构代码时提供帮助的另一个例子(我只在下面总结):

假设你像这样创建了一个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;这里我举一个最小的例子。

让我们从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
 

这里,我们可以说decorator函数修改了say_bye函数,并向其添加了一些额外的代码行。

用于装饰器的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来确保数据封装的原则(这被视为数据与操作这些数据的方法的捆绑)。

当然,这些方法是用于检索数据的getter和用于更改数据的setter。

根据这一原则,类的属性被设置为私有,以隐藏和保护它们不受其他代码的影响。

是的,@property基本上是一种使用getter和setter的python方式。

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_temperature和set_temperature,因为它们是不必要的,而且会污染类名称空间。

python处理上述问题的方法是使用@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

〇注意事项

用于获取值的方法用“@property”修饰。 必须作为setter的方法使用“@temperature”来修饰。如果函数被称为“x”,我们就必须用“@x.setter”来修饰它。 我们写了“两个”方法,名称相同,参数数量不同,“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

这很棒,不是吗:您可以从最简单的实现开始,并且以后可以自由地迁移到属性版本,而不必更改接口!所以属性不仅仅是getter和setter的替代品!

你可以在这里检查这个实现

下面是@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'