鱼C论坛

 找回密码
 立即注册
查看: 2090|回复: 2

python 内置装饰器@property

[复制链接]
发表于 2019-4-15 16:11:06 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 牧码人 于 2019-4-16 11:36 编辑

最近对python的内置装饰器@property我纠结了好久,一直没有弄懂它的原理。

先照例水一波个人对property的浅显的认知,疑问在末尾。
class Student:
    '定义学生知我介绍的类'
    def __init__(self, name):
        self.name = name
        self.intro = "my name is " + self.name


st = Student("Jaki")

print(st.name)
print(st.intro)

st.name = '小甲鱼'
print(st.intro)



>>>Jaki
>>>my name is Jaki
>>>'小甲鱼'
>>>my name is Jaki     

为什么实例st的属性name改成了小甲鱼,但是访问st.intro的时候输出没有改变呢?
>>> st.__dict__
{'name': 'Jaki', 'intro': 'my name is Jaki'}
>>> Student.__dict__
mappingproxy({'__module__': '__main__', '__doc__': '定义学生知我介绍的类', '__init__': <function Student.__init__ at 0x000002AE7BDD7268>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>})
>>> st.name = '小甲鱼'
>>> st.__dict__
{'name': '小甲鱼', 'intro': 'my name is Jaki'}   #可以看到, 改变了实例属性name后,并没有影响到实例属性intro的指向。

代码改进
class Student:
    def __init__(self, name):
        self.name = name
        #self.intro = "my name is "  + self.name


    def intro(self):
        return "my name is " + self.name

st = Student("Jaki")

print(st.name)
print(st.intro())

st.name = '小甲鱼'
print(st.name)
print(st.intro())


>>>Jaki
>>>my name is Jaki
>>>'小甲鱼'
>>>my name is 小甲鱼 


假设实际中有上万行代码在最开始都是用的st.intro来获取实例属性,我们不可能一行一行的去将st.intro改为st.intro(), 所以这里python的内置装饰器property可以派上用场了。

代码继续改进

class Student:
    def __init__(self, name):
        self.name = name
        #self.intro = "my name is "  + self.name

    @property
    def intro(self):
        return "my name is " + self.name

st = Student("Jaki")

print(st.name)
print(st.intro)

st.name = '小甲鱼'
print(st.name)
print(st.intro)


>>>Jaki
>>>my name is Jaki
>>>'小甲鱼'
>>>my name is 小甲鱼 

好吧,以上就是我对property的粗浅认识.

下面直接贴上代码,请大佬帮我分析一下

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)

>>>c = Celsius()   
Setting value


【原文】当一个对象被创建时,__init__()方法被调用。该方法有一行代码self.temperature = temperature。这个任务会自动调用set_temperature()方法。
那应该也会调用get_temperature()方法,因为代码最后一行函数property里将set_temperature和get_temperature都作为参数传入了。

为什么上述代码执行 c = Celsius后会打印出"Setting value" ,而不是"Getting value",或者两者都打印?

temperature = property(get_temperature,set_temperature)

与下式等价,所以"Setting value"和"Getting value"都会打印才对

"""
temperature = property()
temperature = temperature.getter(get_temperature)
temperature = temperature.setter(set_temperature)
"""


原文出处http://python.jobbole.com/81967/#article-comment


有一段对上面property的解释是这样的

property将一些代码(get_temperature和set_temperature)附加到成员属性(temperature)的访问入口。任何获取temperature值的代码都会自动调用get_temperature(),而不是去字典表(__dict__)中进行查找。

这一段总共两句话,这两句话我都没有能理解,汗!!!



想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2019-4-16 14:06:53 | 显示全部楼层
好吧,我这里把stack overflow里找到的property源码贴上,大佬可以在 visualize里可视化调试这个程序,代码运行到第三步往后我就看不懂了。下面是链接。

http://www.pythontutor.com/visualize.html#code=class%20Property%28object%29%3A%0A%20%20%20%20%22Emulate%20PyProperty_Type%28%29%20in%20Objects/descrobject.c%22%0A%0A%20%20%20%20def%20__init__%28self,%20fget%3DNone,%20fset%3DNone,%20fdel%3DNone,%20doc%3DNone%29%3A%0A%20%20%20%20%20%20%20%20self.fget%20%3D%20fget%0A%20%20%20%20%20%20%20%20self.fset%20%3D%20fset%0A%20%20%20%20%20%20%20%20self.fdel%20%3D%20fdel%0A%20%20%20%20%20%20%20%20if%20doc%20is%20None%20and%20fget%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20doc%20%3D%20fget.__doc__%0A%20%20%20%20%20%20%20%20self.__doc__%20%3D%20doc%0A%0A%20%20%20%20def%20__get__%28self,%20obj,%20objtype%3DNone%29%3A%0A%20%20%20%20%20%20%20%20if%20obj%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20self%0A%20%20%20%20%20%20%20%20if%20self.fget%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20AttributeError%28%22unreadable%20attribute%22%29%0A%20%20%20%20%20%20%20%20return%20self.fget%28obj%29%0A%0A%20%20%20%20def%20__set__%28self,%20obj,%20value%29%3A%0A%20%20%20%20%20%20%20%20if%20self.fset%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20AttributeError%28%22can't%20set%20attribute%22%29%0A%20%20%20%20%20%20%20%20self.fset%28obj,%20value%29%0A%0A%20%20%20%20def%20__delete__%28self,%20obj%29%3A%0A%20%20%20%20%20%20%20%20if%20self.fdel%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20AttributeError%28%22can't%20delete%20attribute%22%29%0A%20%20%20%20%20%20%20%20self.fdel%28obj%29%0A%0A%20%20%20%20def%20getter%28self,%20fget%29%3A%0A%20%20%20%20%20%20%20%20return%20type%28self%29%28fget,%20self.fset,%20self.fdel,%20self.__doc__%29%0A%0A%20%20%20%20def%20setter%28self,%20fset%29%3A%0A%20%20%20%20%20%20%20%20return%20type%28self%29%28self.fget,%20fset,%20self.fdel,%20self.__doc__%29%0A%0A%20%20%20%20def%20deleter%28self,%20fdel%29%3A%0A%20%20%20%20%20%20%20%20return%20type%28self%29%28self.fget,%20self.fset,%20fdel,%20self.__doc__%29%0A%20%20%20%20%20%20%20%20%0Aclass%20Student%3A%0A%0A%20%20%20%20def%20__init__%28self,score%3D0%29%3A%0A%20%20%20%20%20%20%20%20self.score%20%3D%20score%0A%0A%20%20%20%20%23%40property%0A%20%20%20%20def%20get_score%28self%29%3A%0A%20%20%20%20%20%20%20%20print%28'Getting%20value'%29%0A%20%20%20%20%20%20%20%20return%20self._score%0A%0A%20%20%20%20%23%40get_score.setter%0A%20%20%20%20def%20set_score%28self,value%29%3A%0A%20%20%20%20%20%20%20%20print%28'Setting%20value'%29%0A%20%20%20%20%20%20%20%20if%20not%20isinstance%28value,int%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28'score%20must%20be%20a%20integer!'%29%0A%20%20%20%20%20%20%20%20if%20value%20%3C%200%20or%20value%20%3E%20100%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28'score%20must%20between%200~100!'%29%0A%20%20%20%20%20%20%20%20self._score%20%3D%20value%0A%0A%20%20%20%20score%20%3D%20Property%28get_score,set_score%29%0A%0As%20%3D%20Student%28%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false
class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject"

    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__)
        
class Student:

    def __init__(self,score=0):
        self.score = score

    #@property
    def get_score(self):
        print('Getting value')
        return self._score

    #@get_score.setter
    def set_score(self,value):
        print('Setting value')
        if not isinstance(value,int):
            raise ValueError('score must be a integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0~100!')
        self._score = value

    score = Property(get_score,set_score)  #这里调用Property

s = Student()
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-1-6 15:08:23 | 显示全部楼层
我的理解是,
因为当你用c = Celsius()实例化一个对象的时候,构造函数__init__()已经默认给temperature赋值是0,那么同时会把temperature=0传给set_temperature()这个函数,这是一个属性修饰函数,当你调用这个函数的时候,就会按照函数语句一句一句执行,这就是为什么会打印Setting value而不会打印Getting value(根本没有调用get_temperature这个函数)。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-12 16:18

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表