鱼C论坛

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

python 内置装饰器@property

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

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

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

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

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

先照例水一波个人对property的浅显的认知,疑问在末尾。


  1. class Student:
  2.     '定义学生知我介绍的类'
  3.     def __init__(self, name):
  4.         self.name = name
  5.         self.intro = "my name is " + self.name


  6. st = Student("Jaki")

  7. print(st.name)
  8. print(st.intro)

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



  11. >>>Jaki
  12. >>>my name is Jaki
  13. >>>'小甲鱼'
  14. >>>my name is Jaki     

复制代码


为什么实例st的属性name改成了小甲鱼,但是访问st.intro的时候输出没有改变呢?


  1. >>> st.__dict__
  2. {'name': 'Jaki', 'intro': 'my name is Jaki'}
  3. >>> Student.__dict__
  4. mappingproxy({'__module__': '__main__', '__doc__': '定义学生知我介绍的类', '__init__': <function Student.__init__ at 0x000002AE7BDD7268>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>})
  5. >>> st.name = '小甲鱼'
  6. >>> st.__dict__
  7. {'name': '小甲鱼', 'intro': 'my name is Jaki'}   #可以看到, 改变了实例属性name后,并没有影响到实例属性intro的指向。

复制代码


代码改进


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


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

  7. st = Student("Jaki")

  8. print(st.name)
  9. print(st.intro())

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


  13. >>>Jaki
  14. >>>my name is Jaki
  15. >>>'小甲鱼'
  16. >>>my name is 小甲鱼
复制代码



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

代码继续改进



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

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

  8. st = Student("Jaki")

  9. print(st.name)
  10. print(st.intro)

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


  14. >>>Jaki
  15. >>>my name is Jaki
  16. >>>'小甲鱼'
  17. >>>my name is 小甲鱼

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

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

  20. class Celsius:
  21.     def __init__(self, temperature = 0):
  22.         self.temperature = temperature

  23.     def to_fahrenheit(self):
  24.         return (self.temperature * 1.8) + 32

  25.     def get_temperature(self):
  26.         print("Getting value")
  27.         return self._temperature

  28.     def set_temperature(self, value):
  29.         if value < -273:
  30.             raise ValueError("Temperature below -273 is not possible")
  31.         print("Setting value")
  32.         self._temperature = value

  33.      temperature = property(get_temperature,set_temperature)

  34. >>>c = Celsius()   
  35. Setting value
复制代码



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

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


  1. temperature = property(get_temperature,set_temperature)

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

  3. """
  4. temperature = property()
  5. temperature = temperature.getter(get_temperature)
  6. temperature = temperature.setter(set_temperature)
  7. """
复制代码



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


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

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

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



小甲鱼最新课程 -> https://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

  1. class Property(object):
  2.     "Emulate PyProperty_Type() in Objects/descrobject"

  3.     def __init__(self, fget=None, fset=None, fdel=None, doc=None):
  4.         self.fget = fget
  5.         self.fset = fset
  6.         self.fdel = fdel
  7.         if doc is None and fget is not None:
  8.             doc = fget.__doc__
  9.         self.__doc__ = doc

  10.     def __get__(self, obj, objtype=None):
  11.         if obj is None:
  12.             return self
  13.         if self.fget is None:
  14.             raise AttributeError("unreadable attribute")
  15.         return self.fget(obj)

  16.     def __set__(self, obj, value):
  17.         if self.fset is None:
  18.             raise AttributeError("can't set attribute")
  19.         self.fset(obj, value)

  20.     def __delete__(self, obj):
  21.         if self.fdel is None:
  22.             raise AttributeError("can't delete attribute")
  23.         self.fdel(obj)

  24.     def getter(self, fget):
  25.         return type(self)(fget, self.fset, self.fdel, self.__doc__)

  26.     def setter(self, fset):
  27.         return type(self)(self.fget, fset, self.fdel, self.__doc__)

  28.     def deleter(self, fdel):
  29.         return type(self)(self.fget, self.fset, fdel, self.__doc__)
  30.         
  31. class Student:

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

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

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

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

  47. s = Student()
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-5-11 11:14

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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