【问题标题】:Why is a class variable defined first before turned into an instance variable in this code?为什么在此代码中先定义类变量,然后再转换为实例变量?
【发布时间】:2017-06-13 10:26:23
【问题描述】:

link 中学习 python 属性装饰器时,我偶然发现了以下代码行:

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)

这段代码基本上可以让您修改/添加限制到Celsius 对象,而无需任何继承Celsius 类的人重构他们的代码。

为什么它先定义一个类变量temperature,而不是让self.temperature = property(get_temperature,set_temperature)就完成了?

编辑:由于 cmets 中的意见冲突,我现在将代码恢复到原始状态,无论是否有错字,以方便人们之后阅读。

【问题讨论】:

  • 应该是__init__ 中的'self._tem...'? _temperature 一开始没有初始化
  • 我记得该属性只适用于新样式类和对象。
  • 您提供的上述链接中有错字。您需要根据最后一段代码更新您的代码。在__init__中应该是self._temperature...
  • @Jacky 这不是错字。请注意,使用上面的代码,您可以在没有错误的情况下执行此操作,这很糟糕:t=Celsius(-500)。通常初始化程序应该使用该属性来获取或设置私有变量,就像任何其他方法一样。
  • @RickTeachey 你是说上面的代码没有正确使用属性方法吗?

标签: python python-2.7 properties


【解决方案1】:

答案就在descriptor protocol and attribute lookup order。如果你这样做了:

class Celsius:
    def __init__(self, temperature): 
        self.temperature = property(get_temperature,set_temperature)
        self.temperature = temperature
# etc etc

它不会像您期望的那样运行。完全没有。

t = Celsius(100)
t.temperature = -500 # NO ERROR! BROKEN!

为什么?因为您使用提供给初始化程序的数字覆盖了属性对象。观察:

get = lambda *args: None # dummy getter
set = lambda *args: None # dummy setter
p = property(get, set) # dummy property

属性应该是Property的实例:

print(type(p).__name__)
# Property

但是你的温度不再是一个属性:

print(type(t.temperature).__name__)
# int

你用这一行覆盖了它:

self.temperature = temperature 
# note that temperature is an int or float

添加一些print 语句,看看发生了什么:

class Celsius:
    def __init__(self, temperature): 
        self.temperature = property(get_temperature,set_temperature)
        print(type(self.temperature).__name__) # Property
        self.temperature = temperature
        print(type(self.temperature).__name__) # no longer a Property!
# etc etc

因此,属性对象需要存储在类级别,这样它就不会在实例级别被覆盖。在实例级别访问类级别属性对象时,会自动调用描述符协议(Property 是一种描述符;描述符具有不寻常的行为,因此请仔细研究它们)。

在此much more detailed answer 了解有关类级别对象与实例级别对象的更多信息。

还要注意,温度需要在初始化器中使用属性设置。不要不要这样做:

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

使用此代码,您可以在没有错误的情况下执行此操作,这很糟糕:t=Celsius(-500)。通常初始化程序应该使用该属性来获取或设置私有变量,就像任何其他方法一样:

        self.temperature = temperature

现在无效的初始温度会导致错误,正如预期的那样。

【讨论】:

  • 对我来说,看起来确实有一个错字,因为在将类实例化为 c 之后,执行 c.temperature 只会在 init 方法。
  • 你错了。
  • 在什么意义上,如果我可以问?事实上,此刻我非常怀疑这段原始代码是否符合习惯。
  • 它显然是旧的。但仅仅是因为它没有使用 @property 装饰器。你错了,因为如果你不使用初始化器内部的设置器,你将能够使用无效值进行初始化,即Celsius(-500)
  • __init__方法中添加self.set_temperature(temperature)是在初始化器中使用setter的正确方法吗?
【解决方案2】:

在上面的 sn-p 中解决了两件事:

1.使用参数化构造函数在对象创建期间设置温度。

def __init__(self, temperature = 0):

在这种情况下,温度为 8 的摄氏度将被创建为:

t = Celsius(8)

2。使用对象属性设置和检索温度使用

temperature = property(get_temperature,set_temperature)

在这种情况下,将创建温度为 8 的摄氏度,设置/检索为:

t = Celsius()
t.set_temperature(8)
print t.get_temperature()

【讨论】:

    【解决方案3】:

    因为方法类属性。而self 不存在于该范围内。

    请注意,该代码已经过时了;你应该使用property 作为装饰器:

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

    【讨论】:

      猜你喜欢
      • 2013-06-09
      • 1970-01-01
      • 1970-01-01
      • 2012-06-23
      • 2013-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-06
      相关资源
      最近更新 更多