Re: [问题] 初学@property之疑问

楼主: uranusjr (←這人是超級笨蛋)   2018-01-02 00:48:04
※ 引述《ar0n77777 (property)》之铭言:
: 各位前辈好 新年快乐
: 这是关于《精通python》上的疑问
: 想请问 class 中 @property 的使用
: @property 这个 decorator 是在其他地方已经被定义了吗
: 不能理解为何可以直接使用这个语法糖
是的
: 还有@property和@name.setter摆放位置的意义
: https://i.imgur.com/fGr96ny.jpg
: 另外就是不能理解property的实际功效和用途
: 麻烦各位了... 非常感谢!!
直接看 decorator 会很像 magic, 我们慢慢来
首先考虑这样一个 class
class Duck:
def __init__(self, name):
self.name = name
这没什么好解释的, 你可以很方便操作这个 attribute
>>> duck = Duck('Donald')
>>> duck.name
'Donald'
>>> duck.name = 'Daffy'
>>> duck.name
'Daffy'
但过了一阵子, 可能你需要在修改鸭子名字时, 同时做某些其他事情
例如确认名字一定是字串, 而且不能超过 100 字之类的
所以你就把 class 改写成这样
class Duck:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, name):
if not isinstance(name, str) or len(name) > 100:
raise ValueError(name)
self._name = name
然后叫大家用 get_name() 与 set_name(), 而不要用本来的 name
但是 1. 一定会有忘记的时候, 2. 原本有的程式都要重写很麻烦
所以这里就是 property 出场的时机
property 是一个内建的 Python 型别 (和 list set 等等类似)
但这个型别特别的地方是应用了一个叫 descriptor 的概念
要从实作细节讲起会花一千字以上, 所以这里就直接看范例与最后结果
class Duck:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, name):
if not isinstance(name, str) or len(name) > 100:
raise ValueError(name)
self._name = name
name = property(get_name, set_name)
在 Duck 上宣告叫 name 的 property, 然后把 get_name 与 set_name 传给它
这个 property 在 Python 里有四种被使用的方法
>>> duck.name # 取值
>>> duck.name = ... # 赋值
>>> del duck.name # 删值
>>> help(duck.name) # 看文件
如果 duck.name 是一个正常普通的值 (例如最开始的那个版本)
上面动作会发生的事情很直观, 你应该也知道
但是如果 name 是一个 descriptor, 则 Python 会去触发它上面的对应 method
取值触发 __get__, 赋值触发 __set__, 其余类推
所以当你取值时, 会发生这样的事情:
1. 你呼叫 duck.name
2. Python 呼叫 duck.name.__get__() -> 会回传一个值
3. Python 把这个值当作 duck.name 的回传值, 把它送给你
赋值则是这样:
1. 你把 'Duffy' 赋给 duck.name
2. Python 不会取代 duck.name, 而是呼叫 duck.name.__set__('Duffy')
回到 property, 这个 class 的实作大致包含下面这样:
class property:
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
# 后面都一样所以略过
def __get__(self, obj):
return self.fget(obj)
# 其他实作差不多, 略过
所以当你呼叫 duck.name 时, 大致会发生这样的事情
1. duck.name
2. duck.name.__get__()
3. Duck.name.__get__(duck) [#1]
4. Duck.name.fget(duck)
5. Duck.get_name(duck) 因为我们把 get_name 传进去了
6. 得到结果
[#1]: 刚好前几篇才提到, 呼叫 instance.method() 等于 Class.method(instance)
这边的状况类似 (有微妙的不同), Python 会自动转换呼叫的格式
注意 5. 等同于 duck.get_name() 所以结果就等于呼叫 property 的第一参数
以上就是 property 的简单原理
但是这样写起来还是有点麻烦, 而且更重要的是, 不直观
当你想知道 duck.name 时, 会需要先发现 name 是一个 property
再根据 property 的引数知道对应传进去的函式, 再去找函式实作, 不太方便
Decorator 就是想办法把这个 property 呼叫变得更简明
当你在一个 method 上加上 @property 时, 例如这样:
class Duck:
# 略
@property
def name(self):
return self._name
Python 会做以下的事情:
1. 根据 method 名 (这里就是 name) 用一个同名 property 替代掉
2. 被替代掉的函式就当作该 property 的 fget 引数
3. property 的 doc 引数就是原本 getter 的 docstring
类似地, property.setter 会把被装饰的函式设成该 property 的 setter
所以下面的实作
class Duck:
@property
def name(self):
return self._name
@name.setter
def name(self, name): # 顺带一提其实这个函式叫什么根本不重要
self._name = name
大致等于
class Duck:
def getname(self):
return self._name
# @property 的作用
name = property(fget=getname, doc=getname.__doc__)
del getname
def setname(self, value):
self._name = name
# @name.setter 的作用
name.fset = setname
del setname
实际上因为 decorator 本身可以在赋名之前就作用
所以可以取和 property 一样的名字
但是要讨论这个就又要一千字, 所以这里就不讲到那里
如果有兴趣的话可以自己当成未来课题慢慢研究
另外从上面可以看到, property class 还有一个 deleter 可以设
所以实际上也还有一个 @property.deleter, 只是比较不常用
甚至实务上其实最常见的实作也只会用到 getter 而已
当你用到 setter 其实常常就代表需要重构了
作者: ar0n77777 (property)   2018-01-02 01:20:00
大致上理解了,感谢u大解答!!太感谢了
作者: GNUGCC (-std=c++14)   2018-08-10 00:59:00
void main(void) 的写法是可行的唷^^虽然这个写法较传统,但是语法与文法都正确哦^^目前我使用的 Visual C++ 都接受 void main(void) 与int main(void),各位可以把 C++ 专案改成原生 C++ 类型来用 void main(void) 来写发现也可通过编译.这个就是 Visual C++ 的弹性.
作者: aszx4510 (wind)   2018-01-02 06:22:00
感谢教学
作者: billy4195 (Billy)   2018-01-02 07:13:00
推个
作者: timTan (用口头禅区分年记)   2018-01-04 12:23:00
又帅又清楚,推推推
作者: henry8168 (番薯猴)   2018-01-04 12:27:00
用心推推
作者: fantasymaker   2018-01-05 16:34:00
作者: twtkacc (Tech)   2018-01-07 02:32:00

Links booklink

Contact Us: admin [ a t ] ucptt.com