2020年9月17日星期四

[課後作業] 第046講:魔法方法:描述符(Property的原理) |課後測試題及答案




《零基礎入門學習Python》視頻下載地址:傳送門s
#Z("Of^gG*ar,V7}v:).L2uDX8
測試題(筆試,不能上機哦~):K=*q&
~ g8:5Ca{I;OJVMA0)Y2sdo
0.請盡量用自己的語言來解釋什麼是描述符(不要搜索來的答案,用自己的話解釋)?8iB%PZ3
AY82;DMav9U1Tm3{g%$
Ms*,` }^xKi(=q[ZeHL {
1.描述符類中,分別通過哪些魔法方法來實現對屬性的get、set和delete操作的?ixOa1
oJj>_ig.~1Bat]&3MnfZU [7{#Y:
y2pY47LVPmcz ]UNw^=A">
2.請問以下代碼,分別調用test.a和test.x,哪個會打印“getting…”? 
wD_%ck=AZ
q:GMV^[4$N+OxUDC=Fy|ibP5u @nz

  1. >>> class MyDes:
  2.         def __get__(self, instance, owner):
  3.                 print("getting...")

  4. >>> class Test:
  5.         a = MyDes()
  6.         x = a

  7. >>> test = Test()
複製代碼

tNKJoC&)mrAGs.6aen,F<bH[W8u
3.請問以下代碼會打印什麼內容?版權屬於:bbs.fishc.com
ZWH|0~q%Kc_FQjo$#a6C[VEx
  1. class MyDes:
  2.     def __init__(self, value = None):
  3.         self.val = value

  4.     def __get__(self, instance, owner):
  5.         return self.val - 20

  6.     def __set__(self, instance, value):
  7.         self.val = value + 10
  8.         print(self.val)

  9. class C:
  10.     x = MyDes()

  11. if __name__ == '__main__': # 該模塊被執行的話,執行下邊語句。
  12.     c = C()
  13.     cx = 10
  14.     print(cx)
複製代碼

s7$w)]'RK8bXVCMr%^+x
4.請問以下代碼會打印什麼內容?權屬於:bbs.fishc.com
fj+5~{N9s?z3"nXS1^e_GK`#UoHIi
  1. >>> class MyDes:
  2.         def __init__(self, value = None):
  3.                 self.val = value
  4.         def __get__(self, instance, owner):
  5.                 return self.val ** 2

  6. >>> class Test:
  7.         def __init__(self):
  8.                 self.x = MyDes(3)

  9. >>> test = Test()
  10. >>> test.x
複製代碼

.ylt1mn5>0kCI-eS!v,paXR#Z4A
Xn;[%FZIC.,{+bd83we4sG "gLz
動動手(一定要自己動手試試哦~):KFWa4`L9-
(;)%3WKhAd*zN<nCp ^RZ>J,L0'
0.按要求編寫描述符MyDes:當類的屬性被訪問、修改或設置的時候,分別做出提醒。Qr0+n6]pc
!Z.9">b7AM 4DiR&NO_'$ILh
程序實現如下:版權屬於:bbs.fishc.com
jpxrkgih@&D{K-[L}CyTY+cON0
  1. >>> class Test:
  2.         x = MyDes(10, 'x')

  3. >>> test = Test()
  4. >>> y = test.x
  5. 正在獲取變量: x
  6. >>> y
  7. 10
  8. >>> test.x = 8
  9. 正在修改變量: x
  10. >>> del test.x
  11. 正在刪除變量: x
  12. 噢~這個變量沒法刪除~
  13. >>> test.x
  14. 正在獲取變量: x
  15. 8
複製代碼

<|Z8O?}W=R`-_lY7Sn^[T9ViC4H
{2L(s|6olxKP%$A.>a-#0u
1.按要求編寫描述符MyDes:記錄指定變量的讀取和寫入操作,並將記錄以及觸發時間保存到文件:record.txt
<Y0C|,aWgp^BA_uol:>Q'
程序實現如下:Powered by bbs.fishc.com
1(+FlB'pm0=ad f6r&32z8[n:U
  1. >>> class Test:
  2.         x = Record(10, 'x')
  3.         y = Record(8.8, 'y')

  4. >>> test = Test()
  5. >>> test.x
  6. 10
  7. >>> test.y
  8. 8.8
  9. >>> test.x = 123
  10. >>> test.x = 1.23
  11. >>> test.y = "I love FishC.com!"
  12. >>>
複製代碼

產生文件:record.txt Powered by bbs.fishc.com
R# -ou?(Td]Y4+Ce1t^yN

'?p_GZ1)gs2*$CirIL|N.6
@[C E1YV:k`{H'2mb%SQvg5FRp+>
IkZ*>cTJ j'!2dCif).h
2.再來一個有趣的案例:編寫描述符MyDes,使用文件來存儲屬性,屬性的值會直接存儲到對應的pickle(醃菜,還記得嗎?)的文件中。如果屬性被刪除了,文件也會同時被刪除,屬性的名字也會被註銷。r+q@>f(h)_
qA43G5gfPdor!#{lXS2`
舉個栗子:來自:bbs.fishc.com
  1. >>> class Test:
  2.         x = MyDes('x')
  3.         y = MyDes('y')
  4.         
  5. >>> test = Test()
  6. >>> test.x = 123
  7. >>> test.y = "I love FishC.com!"
  8. >>> test.x
  9. 123
  10. >>> test.y
  11. 'I love FishC.com!'
複製代碼

產生對應的文件存儲變量的值:版權屬於:bbs.fishc.com
g<CaxJTHbcdKImnM8q:-9

iQVFOLCh*]j?Y4|)vsgrG@U
如果我們刪除x屬性:Powered by bbs.fishc.com
  1. >>> del test.x
  2. >>>
複製代碼

對應的文件也不見了:版權屬於:bbs.fishc.com
u-$sg>KeBtDnQ#JZ[zG&.TXEWA:+'|

$!Yoh@e"b1fasT5r><&Q?
hb?VL;x`)U'!=rTacC({Ydq>OSm[
回复您的答案即可查看參考答案! u[93g%k@_,
p>YqwzO9UDdv $F=rfN5A+V
Ja'P)=<24q!~dQ:_#yOw
測試題答案:Powered by bbs.fishc.com

本帖隱藏的內容

來自:bbs.fishc.com
0.請盡量用自己的語言來解釋什麼是描述符(不要搜索來的答案,用自己的話解釋)?uZiFY&A-
O+(UD6B,f S&HX7b]Ajl?<K8xqe
答:有時候,某個應用程序可能會有一個相當微妙的需求,需要你設計一些更為複雜的操作來響應(例如每當屬性被訪問時,你也許想創建一個日誌記錄)。最好的解決方案就是編寫一個用於執行這些“更複雜的操作”的特殊函數,然後指定它在屬性被訪問時運行。那麼一個具有這種函數的對象被稱之為描述符。Lj+!Ixns
tWj<vQpJzd4[H>LX`#KD&F)0_gE|A
往再簡單了說,描述符就是一個類,一個至少實現__get__()、__set__()或__delete__ ()三個特殊方法中的任意一個的類。v_wN0r.uL2
B!DisA8=XfU6]upS;)F+~Q?gq1OTd
bT6p$@E]!c?-eMH|2>_<S'B(
1.描述符類中,分別通過哪些魔法方法來實現對屬性的get、set和delete操作的?O]9-tS<
CuSQA5*[O;qDL%jzUG|+2`4.nV
答:__get__、__set__和__delete__ SwBx?Q7!
8>]Da3xT-[ ?GQK.Lo9c_zY"Nbq
__get__(self, instance, owner) <
-用於訪問屬性,它返回屬性的值版權屬於:bbs.fishc.com
s;j1TGb2,nml>^wH?rWE'g
__set__(self, instance, value) im;hH%Fx
-將在屬性分配操作中調用,不返回任何內容KMy3[
]x~Cz#$lm6JAqG!j'}r&c
__delete__(self, instance) 21+VuZ&i
-控制刪除操作,不返回任何內容來自:bbs.fishc.com
(9nK6DB*4 #y{ghe`l>)JZt}Nk
E!hK<:pkV,zI5yj|PgfT*Uw
@_Joa9O>{ELdXx^sl0IW<DNeiRqy?
2.請問以下代碼,分別調用test.a和test.x,哪個會打印“getting…”? 4^v)3*?f
#~B2k%uE{!bHjGC=g]tXK7yqN+9?
  1. >>> class MyDes:
  2.         def __get__(self, instance, owner):
  3.                 print("getting...")

  4. >>> class Test:
  5.         a = MyDes()
  6.         x = a

  7. >>> test = Test()
複製代碼

答:都會打印滴。版權屬於:bbs.fishc.com
49m(Wkud8Zse Xy%j|^rKB#"Viw
dC2}Bjcm]"#=`Z6F-X*^|k
3.請問以下代碼會打印什麼內容?
-'%!G {3u_<)`PZoaMtzV56ime +~r1
  1. class MyDes:
  2.     def __init__(self, value = None):
  3.         self.val = value

  4.     def __get__(self, instance, owner):
  5.         return self.val - 20

  6.     def __set__(self, instance, value):
  7.         self.val = value + 10
  8.         print(self.val)

  9. class C:
  10.     x = MyDes()

  11. if __name__ == '__main__': # 該模塊被執行的話,執行下邊語句。
  12.     c = C()
  13.     cx = 10
  14.     print(cx)
複製代碼

答:需要注意的是print(cx)訪問了c的x屬性,因此值減20。j&LBv
nfG!`}c8'l^EAd6,BWoVu
  1. >>>
  2. 20
  3. 0
複製代碼

gM)U|Ddh2 :m4F%G*fq5?sP
4.請問以下代碼會打印什麼內容?
9#bP-7=iZRm(0I|'jG&^H>!~kx$S8t
  1. >>> class MyDes:
  2.         def __init__(self, value = None):
  3.                 self.val = value
  4.         def __get__(self, instance, owner):
  5.                 return self.val ** 2

  6. >>> class Test:
  7.         def __init__(self):
  8.                 self.x = MyDes(3)

  9. >>> test = Test()
  10. >>> test.x
複製代碼

答:如果你認為小甲魚考的是3 de平方== 9,那你就too young too simple了!這其實是一個“陷阱”,我們先來看下會打印什麼:Gk:*d
?F$!hvL*<2i.|e718V:YfP
  1. >>> test.x
  2. <__main__.MyDes object at 0x1058e6f60>
複製代碼

如你所見,訪問實例層次上的描述符x,只會返回描述符本身。為了讓描述符能夠正常工作,它們必須定義在類的層次上。如果你不這麼做,那麼Python無法自動為你調用__get__和__set__方法。`5A-"4_Bh

版權屬於:bbs.fishc.com
" klaE.QtIw=i>OV)}[WvqdA0eNRy~
動動手答案:Powered by bbs.fishc.com

本帖隱藏的內容

來自:bbs.fishc.com
0.按要求編寫描述符MyDes:當類的屬性被訪問、修改或設置的時候,分別做出提醒。!;lu^w
*lzwt{cV]p>KFR$CTULP
答:其實大家如果自己認真思考了代碼,會發現我們這裡描述符起到的作用是間接地保存指定變量的數據。
_ J7g3}6p:~25TB84<dN(j!*
代碼清單:來自:bbs.fishc.com
0^E~a1`i,O}3$!F5"4xg=#P.&*)
  1. class MyDes:
  2.     def __init__(self, initval=None, name=None):
  3.         self.val = initval
  4.         self.name = name

  5.     def __get__(self, instance, owner):
  6.         print("正在獲取變量:", self.name)
  7.         return self.val

  8.     def __set__(self, instance, value):
  9.         print("正在修改變量:", self.name)
  10.         self.val = value

  11.     def __delete__(self, instance):
  12.         print("正在刪除變量:", self.name)
  13.         print("噢~這個變量沒法刪除~")
複製代碼

^*?WoQNZ&}.$Pcq<y6w8_E'[
"Mi7RWXefQ;j&?Pv3c9ZbO~[(I6z|
1.按要求編寫描述符MyDes:記錄指定變量的讀取和寫入操作,並將記錄以及觸發時間保存到文件(record.txt)y~N*Zx
d(+F~pAS!f5CG3`#hbsQEmc
答:這道題考察的點比較多,例如字符串的轉換、文件的操作、time模塊的用法、描述符……大家哪裡不會補哪裡~ ~$diR0Zelr
i*vu {yw+bseX5L-]=k;BVhOxPIU>
代碼清單:Powered by bbs.fishc.com
IT>O]lu2CqkZ."8P~^cLhzY9Sy'
  1. import time

  2. class Record:
  3.     def __init__(self, initval=None, name=None):
  4.         self.val = initval
  5.         self.name = name
  6.         self.filename = "record.txt"

  7.     def __get__(self, instance, owner):
  8.         with open(self.filename, 'a', encoding='utf-8') as f:
  9.             f.write("%s 變量於北京時間%s 被讀取,%s = %s\n" % \
  10.                     (self.name, time.ctime(), self.name, str(self.val)))
  11.         return self.val

  12.     def __set__(self, instance, value):
  13.         filename = "%s_record.txt" % self.name
  14.         with open(self.filename, 'a', encoding='utf-8') as f:
  15.             f.write("%s 變量於北京時間%s 被修改, %s = %s\n" % \
  16.                     (self.name, time.ctime(), self.name, str(value)))
  17.         self.val = value
複製代碼

j:h-vyNKHi|G>5,'&FzrCWL
|x$])Tv~!"@k}C'u<Kn_+
2.再來一個有趣的案例:編寫描述符MyDes,使用文件來存儲屬性,屬性的值會直接存儲到對應的pickle(醃菜,還記得嗎?)的文件中。如果屬性被刪除了,文件也會同時被刪除,屬性的名字也會被註銷。T 45mJ
`~({# P?p8sHeLU7IC>gYd;Sr
代碼清單:版權屬於:bbs.fishc.com
Q6'!@Eva,Cdi<1]5+B[PSLY p:IH
  1. import os
  2. import pickle

  3. class MyDes:
  4.     saved = []

  5.     def __init__(self, name = None):
  6.         self.name = name
  7.         self.filename = self.name + '.pkl'

  8.     def __get__(self, instance, owner):
  9.         if self.name not in MyDes.saved:
  10.             raise AttributeError("%s 屬性還沒有賦值!" % self.name)

  11.         with open(self.filename, 'rb') as f:
  12.             value = pickle.load(f)

  13.         return value

  14.     def __set__(self, instance, value):
  15.         with open(self.filename, 'wb') as f:
  16.             pickle.dump(value, f)
  17.             MyDes.saved.append(self.name)

  18.     def __delete__(self, instance):
  19.         os.remove(self.filename)
  20.         MyDes.saved.remove(self.name)
複製代碼

3.請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

小甲魚希望你認真對待作業就像你希望小甲魚推出高質量視頻一樣渴望^_^

版權屬前面的课程中我们提到了 property 函数,那 property 到底是怎样实现的呢?今天我们谈论的问题是描述符,
描述符就是将某种特殊类型的类的实例指派给另一个类的属性。大家对于这个定义可能还不是很理解,等会会举例说明。首先,什么是特殊类型呢?特殊类型的要求是至少要实现以下三个方法其中一个或全部实现。
(一)•__get__(self, instance, owner)
用于访问属性,它返回属性的值
(二)•__set__(self, instance, value)
将在属性分配操作中调用,不返回任何内容
(三)•__delete__(self, instance)
控制删除操作,不返回任何内容
这三个方法和我们上节课提到的 __getattr__, __setattr__, delattr__是很相像的,但是这三个我们称之为属于描述符属性的方法。__get__用于访问属性的时候直接调用,__set__用于将属性分配,也就是赋值的时候被调用,__delete__用于删除属性的时候被调用。
先来一个最直观的例子:
  1. >>> class MyDescriptor:
  2. def __get__(self, instance, owner):
  3. print("getting...", self, instance, owner)
  4. def __set__(self, instance, value):
  5. print("setting...", self, instance, value)
  6. def __delete__(self, instance):
  7. print("deleting...", self, instance)
  8. >>> class Test:
  9. x = MyDescriptor()
先来写一个描述符 MyDescriptor,并且把所有方法的参数给打印出来。
再来一个真正的Test类来测试一下,就给一个属性 x ,把 MyDescriptor() 的实例指派给Test类的属性 x。我们就说 MyDescriptor 类就是 x 的描述符。(是不是一下子感觉到了 property 的影子)。
我们下面实例化 Test 类,对 x 属性进行各种操作,看看描述符类 MyDescriptor 会有怎样的响应。
  1. >>> test = Test()
  2. >>> test.x
  3. getting... <__main__.MyDescriptor object at 0x000002681DA3D1D0> <__main__.Test object at 0x000002681DA25390> <class '__main__.Test'>
  4. >>> test
  5. <__main__.Test object at 0x000002681DA25390>
  6. >>> Test
  7. <class '__main__.Test'>
  8. >>> test.x = "x-man"
  9. setting... <__main__.MyDescriptor object at 0x000002681DA3D1D0> <__main__.Test object at 0x000002681DA25390> x-man
  10. >>> del test.x
  11. deleting... <__main__.MyDescriptor object at 0x000002681DA3D1D0> <__main__.Test object at 0x000002681DA25390>
我们尝试直接打印 test.x ,我们看到会调用描述符的 __get__,并且参数的意义也很明确, self 是描述符类本身的实例,instance 参数是 它的拥有者 Test 的实例 test,我们直接打印 test,就和 instance 的内容一样,然后 owner 就是它的拥有者 Test 类本身,我们直接打印 Test,就和 owner 的内容一样。
然后赋值(test.x = "x-man")的时候,就会调用描述符的 __set__,删除(del test.x)的时候也是一样,调用__delete__。
我们刚才说过了,只要我们弄清楚描述符,那么 property 的秘密就不再是秘密了,没错,property 就是一个描述符类,我们这里就来定义一个属于自己的 property(MyProperty),来实现property的所有功能:(我们这里定义的MyProperty只是把 property 的功能进行照搬,大家可以加入自己的创意)
  1. >>> class MyProperty:
  2. def __init__(self, fget = None, fset = None, fdel = None):
  3. self.fget = fget
  4. self.fset = fset
  5. self.fdel = fdel
  6. def __get__(self, instance, owner):
  7. return self.fget(instance)
  8. def __set__(self, instance, value):
  9. self.fset(instance, value)
  10. def __delete__(self, instance):
  11. self.fdel(instance)
  12. >>> class C:
  13. def __init__(self):
  14. self._x = None
  15. def getx(self):
  16. return self._x
  17. def setx(self, value):
  18. self._x = value
  19. def delx(self):
  20. del self._x
  21. x = MyProperty(getx, setx, delx)
  22. >>> c = C()
  23. >>> c.x = "x-man"
  24. >>> c._x
  25. 'x-man'
  26. >>> del c.x
  27. >>> c._x
  28. Traceback (most recent call last):
  29. File "<pyshell#62>", line 1, in <module>
  30. c._x
  31. AttributeError: 'C' object has no attribute '_x'
接下来,我们来做一个练习:
先定义一个温度类,然后定义两个描述符类用于描述摄氏度和华氏度两个属性
要求个属性会自动进行转换,也就是说你可以给摄氏度这个属性赋值,然后打印的华氏度属性是自动转换后的结果。
公式:摄氏度 * 1.8 + 32 = 华氏度
  1. class Celsius:
  2. def __init__(self, value = 26.0):
  3. self.value = float(value)
  4. def __get__(self, instance, owner):
  5. return self.value
  6. def __set__(self, instance, value):
  7. self.value = float(value)
  8. class Fahrenheit:
  9. def __get__(self, instance, owner):
  10. return instance.cel * 1.8 +32
  11. def __set__(self, instance, value):
  12. instance.cel = (float(value) - 32) / 1.8
  13. class Temperature:
  14. cel = Celsius()
  15. fah = Fahrenheit()
  1. >>> temp = Temperature()
  2. >>> temp.cel
  3. 26.0
  4. >>> temp.fah
  5. 78.80000000000001
  6. >>> temp.fah = 100
  7. >>> temp.cel
  8. 37.77777777777778
c

0 留言:

發佈留言