python 内置装饰器@property
本帖最后由 牧码人 于 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__)中进行查找。
这一段总共两句话,这两句话我都没有能理解,汗!!!
好吧,我这里把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() 我的理解是,
因为当你用c = Celsius()实例化一个对象的时候,构造函数__init__()已经默认给temperature赋值是0,那么同时会把temperature=0传给set_temperature()这个函数,这是一个属性修饰函数,当你调用这个函数的时候,就会按照函数语句一句一句执行,这就是为什么会打印Setting value而不会打印Getting value(根本没有调用get_temperature这个函数)。
页:
[1]