2020年9月16日星期三

[課後作業] 第045講:魔法方法:屬性訪問|課後測試題及答案 




《零基礎入門學習Python》視頻下載地址:傳送門
[%&5F,Zw
測試題:@~NcoFv#-R
kWmU7l^[
0.請問以下代碼的作用是什麼?這樣寫正確嗎?(如果不正確,請改正)&C[G3~"
QHePowered by bbs.fishc.com 3來自:bbs.fishc.com 0h

  1. def __setattr__(self, name, value):
  2.         self.name = value + 1
複製代碼

['v來自:bbs.fishc.com @3>
1.自定義該類的屬性被訪問的行為,你應該重寫哪個魔法方法?b#z>o0
e.KE;rMzj
6Gq1?8
2.在不上機驗證的情況下,你能推斷以下代碼分別會顯示什麼嗎?
~6j,ku"g
oQ=Y3r`5來自:bbs.fishc.com W
  1. >>> class C:
  2.         def __getattr__(self, name):
  3.                 print(1)
  4.         def __getattribute__(self, name):
  5.                 print(2)
  6.         def __setattr__(self, name, value):
  7.                 print(3)
  8.         def __delattr__(self, name):
  9.                 print(4)

  10.                
  11. >>> c = C()
  12. >>> cx = 1
  13. # 位置一,請問這裡會顯示什麼?
  14. >>> print(cx)
  15. # 位置二,請問這裡會顯示什麼?
複製代碼

*45G8!c
3.在不上機驗證的情況下,你能推斷以下代碼分別會顯示什麼嗎?dXw1:#=SBP
>7O(yL6
  1. >>> class C:
  2.         def __getattr__(self, name):
  3.                 print(1)
  4.                 return super().__getattr__(name)
  5.         def __getattribute__(self, name):
  6.                 print(2)
  7.                 return super().__getattribute__(name)
  8.         def __setattr__(self, name, value):
  9.                 print(3)
  10.                 super().__setattr__(name, value)
  11.         def __delattr__(self, name):
  12.                 print(4)
  13.                 super().__delattr__(name)

  14.                
  15. >>> c = C()
  16. >>> cx
複製代碼

i4rck=dgZI
4.請指出以下代碼的問題所在:d版權屬於:bbs.fishc.com &b`Z~yp
r>YE*sZN
  1. class Counter:
  2.         def __init__(self):
  3.                 self.counter = 0
  4.         def __setattr__(self, name, value):
  5.                 self.counter += 1
  6.                 super().__setattr__(name, value)
  7.         def __delattr__(self, name):
  8.                 self.counter -= 1
  9.                 super().__delattr__(name)
複製代碼

G ,elX4來自:bbs.fishc.com tJ
動動手:5k&來自:bbs.fishc.com :TN[uJ
];V0&
0.按要求重寫魔法方法:當訪問一個不存在的屬性時,不報錯且提示“該屬性不存在!” #syESWi1(
205*A
Kv{z1
1.編寫Demo類,使得下邊代碼可以正常執行:
XzOTd`&
|M~![K:UJC
  1. >>> demo = Demo()
  2. >>> demo.x
  3. 'FishC'
  4. >>> demo.x = "X-man"
  5. >>> demo.x
  6. 'X-man'
複製代碼

UAlse7CS)
2.修改上邊【測試題】第4題,使之可以正常運行:編寫一個Counter類,用於實時檢測對像有多少個屬性。0]+.FCJ
v5dEr-iM
程序實現如下:Z<zQ%-
  1. >>> c = Counter()
  2. >>> cx = 1
  3. >>> c.counter
  4. 1
  5. >>> cy = 1
  6. >>> cz = 1
  7. >>> c.counter
  8. 3
  9. >>> del cx
  10. >>> c.counter
  11. 2
複製代碼

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


回复您的答案即可查看參考答案!uxrvR62iAZ
C|^yDTLtp
G^1[E&B
測試題答案:

本帖隱藏的內容

[2&j=|
0.請問以下代碼的作用是什麼?這樣寫正確嗎?(如果不正確,請改正)=H7@%NA5dq
rOs&"%IXbq
  1. def __setattr__(self, name, value):
  2.         self.name = value + 1
複製代碼

答:這段代碼試圖在對象的屬性發生賦值操作的時候,將實際的值+1賦值給相應的屬性。但這麼寫法是錯誤的,因為每當屬性被賦值的時候, __setattr__()會被調用,而裡邊的self.name = value + 1語句又會再次觸發__setattr__()調用,導致無限遞歸。@Powered by bbs.fishc.com BntD 2}
:@?bE
代碼應該這樣寫:kJSbWVtH
  1. def __setattr__(self, name, value):
  2.         self.__dict__[name] = value + 1
複製代碼

或者:xBp0LSolG
  1. def __setattr__(self, name, value):
  2.         super().__setattr__(name, value+1)
複製代碼

qo.<2fg@
1.自定義該類的屬性被訪問的行為,你應該重寫哪個魔法方法?(PtusW
}2RJ&A
答:__getattribute__(self, name) R^M版權屬於:bbs.fishc.com XEhsZ
a5來自:bbs.fishc.com _TJdv6?
8E6F1XB
2.在不上機驗證的情況下,你能推斷以下代碼分別會顯示什麼嗎??JTEMiW|G2
5SLUM?*;
  1. >>> class C:
  2.         def __getattr__(self, name):
  3.                 print(1)
  4.         def __getattribute__(self, name):
  5.                 print(2)
  6.         def __setattr__(self, name, value):
  7.                 print(3)
  8.         def __delattr__(self, name):
  9.                 print(4)

  10.                
  11. >>> c = C()
  12. >>> cx = 1
  13. # 位置一,請問這裡會顯示什麼?
  14. >>> print(cx)
  15. # 位置二,請問這裡會顯示什麼?
複製代碼

答:位置一會顯示3,因為cx = 1是賦值操作,所以會訪問__setattr__()魔法方法;位置二會顯示2和None,因為x是屬於實例對象c的屬性,所以cx是訪問一個存在的屬性,因此會訪問__getattribute__()魔法方法,但我們重寫了這個方法,使得它不能按照正常的邏輯返回屬性值,而是打印一個2代替,由於我們沒有寫返回值,所以緊接著返回None並被print()打印出來。O1m~r#Z>$
z[W=-'
<Z;L4qW>
3.在不上機驗證的情況下,你能推斷以下代碼分別會顯示什麼嗎?;c2>I]J
f=cGY}
  1. >>> class C:
  2.         def __getattr__(self, name):
  3.                 print(1)
  4.                 return super().__getattr__(name)
  5.         def __getattribute__(self, name):
  6.                 print(2)
  7.                 return super().__getattribute__(name)
  8.         def __setattr__(self, name, value):
  9.                 print(3)
  10.                 super().__setattr__(name, value)
  11.         def __delattr__(self, name):
  12.                 print(4)
  13.                 super().__delattr__(name)

  14.                
  15. >>> c = C()
  16. >>> cx
複製代碼

答:在不上機的情況下,我相信80%以上的魚油很難猜到正確的答案T_T =+BPNjLlUT
qht]1
  1. >>> c = C()
  2. >>> cx
  3. 2
  4. 1
  5. Traceback (most recent call last):
  6.   File "<pyshell#31>", line 1, in <module>
  7.     cx
  8.   File "<pyshell#29>", line 4, in __getattr__
  9.     return super().__getattr__(name)
  10. AttributeError: 'super' object has no attribute '__getattr__'
複製代碼

為什麼會如此顯示呢?我們來分析下:首先cx會先調用__getattribute__()魔法方法,打印2;然後調用super().__getattribute__(),找不到屬性名x,因此會緊接著調用__getattr__() ,於是打印1 ;但是你猜到了開頭沒猜到結局……當你希望最後以super().__getattr__()終了的時候,Python竟然告訴你AttributeError,super對象木有__getattr__ !A6pW4
}a>EISqDC
求證:O~U{a1
  1. >>> dir(super)
  2. ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', ' __le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__' , '__thisclass__']
複製代碼

b+)tj
4.請指出以下代碼的問題所在:
SU7n1B"
  1. class Counter:
  2.         def __init__(self):
  3.                 self.counter = 0
  4.         def __setattr__(self, name, value):
  5.                 self.counter += 1
  6.                 super().__setattr__(name, value)
  7.         def __delattr__(self, name):
  8.                 self.counter -= 1
  9.                 super().__delattr__(name)
複製代碼

答:初學者重寫屬性魔法方法很容易陷入的一個誤區就是木有“觀前顧後”。tR){lP
)7v|^1T!R
以下註釋:!aAMZ
  1. class Counter:
  2.         def __init__(self):
  3.                 self.counter = 0 # 這裡會觸發__setattr__ 調用
  4.         def __setattr__(self, name, value):
  5.                 self.counter += 1
  6. “””既然需要__setattr__ 調用後才能真正設置self.counter 的值,所以這時候self.counter 還沒有定義,所以沒法+= 1,錯誤的根源。”””
  7.                 super().__setattr__(name, value)
  8.         def __delattr__(self, name):
  9.                 self.counter -= 1
  10.                 super().__delattr__(name)
複製代碼

,6tn%0?I7l
<?Powered by bbs.fishc.com H!ioZ7
動動手答案:

本帖隱藏的內容

P)`<w#F24
0.按要求重寫魔法方法:當訪問一個不存在的屬性時,不報錯且提示“該屬性不存在!” Mo{q8p
).HsR8:MI
代碼清單:INU1R
  1. >>> class Demo:
  2.         def __getattr__(self, name):
  3.                 return '該屬性不存在!'

  4.         
  5. >>> demo = Demo()
  6. >>> demo.x
  7. '該屬性不存在!'
複製代碼

Hf版權屬於:bbs.fishc.com paUdV'
1.編寫Demo類,使得下邊代碼可以正常執行::hV$9%k
:r6iM
  1. >>> demo = Demo()
  2. >>> demo.x
  3. 'FishC'
  4. >>> demo.x = "X-man"
  5. >>> demo.x
  6. 'X-man'
複製代碼

WKPo&F'e
代碼清單:L}( $KE4:
  1. >>> class Demo:
  2.         def __getattr__(self, name):
  3.                 self.name = 'FishC'
  4.                 return self.name
複製代碼

g)A(SsM*3B
2.修改上邊【測試題】第4題,使之可以正常運行:編寫一個Counter類,用於實時檢測對像有多少個屬性。
d5版權屬於:bbs.fishc.com ^ ]
程序實現如下:&BQ0|l-
  1. >>> c = Counter()
  2. >>> cx = 1
  3. >>> c.counter
  4. 1
  5. >>> cy = 1
  6. >>> cz = 1
  7. >>> c.counter
  8. 3
  9. >>> del cx
  10. >>> c.counter
  11. 2
複製代碼

'7pPowered by bbs.fishc.com O^
代碼清單:WP"OqI3%
  1. class Counter:
  2.         def __init__(self):
  3.                 super().__setattr__('counter', 0)
  4.         def __setattr__(self, name, value):
  5.                 super().__setattr__('counter', self.counter + 1)
  6.                 super().__setattr__(name, value)
  7.         def __delattr__(self, name):
  8.                 super().__setattr__('counter', self.counter - 1)
  9.                 super().__delattr__(name)
複製代碼

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

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

我們這節課說說魔法方法關於屬性訪問的應用。我們知道可以使用點操作符(.)的形式去訪問對象屬性。我們在類與對象相關的BIF這一節中,我們可以使用幾個BIF有禮貌的去訪問屬性,例如:
  1. >>> class C:
  2. def __init__(self):
  3. self.x = 'x-man'
  4. >>> c = C()
  5. >>> c.x
  6. 'x-man'
  7. >>> getattr(c, 'x', '没有这个属性')
  8. 'x-man'
  9. >>> getattr(c, 'y', '没有这个属性')
  10. '没有这个属性'
另外,我們還介紹過setattr , delattr分別是設置屬性和刪除屬性,忘了的話可以回頭看一下筆記然後還介紹了property函數的用法,property使得我們以屬性的方法去訪問屬性。例如:
  1. >>> class C:
  2. def __init__(self, size = 10):
  3. self.size = size
  4. def getSize(self):
  5. return self.size
  6. def setSize(self, value):
  7. self.size = value
  8. def delSize(self):
  9. del self.size
  10. x = property(getSize, setSize, delSize)
  11. >>> c = C()
  12. >>> c.x
  13. 10
  14. >>> c.x = 1
  15. >>> c.size
  16. 1
  17. >>> del c.x
  18. >>> c.size
  19. Traceback (most recent call last):
  20. File "<pyshell#24>", line 1, in <module>
  21. c.size
  22. AttributeError: 'C' object has no attribute 'size'
關於屬性訪問也有相應的魔法方法來管理,通過這些魔法方法的重寫可以隨心所欲的控制對象的屬性訪問。下面是今天要講解的四個魔法方法:
(一)__getattr__(self, name)
–定義當用戶試圖獲取一個不存在的屬性時的行為
(二)__getattribute__(self, name)
–定義當該類的屬性被訪問時的行為
(三)__setattr__(self, name, value)
–定義當一個屬性被設置時的行為
(四)__delattr__(self, name)
–定義當一個屬性被刪除時的行為
也就是說,我們只要重寫以上四個魔法方法,就可以空置對象的屬性訪問了,我們舉例說明來測試一下這四個魔法方法的前後關係、因果關係:
  1. >>> class C:
  2. def __getattr__(self, name):
  3. print("getattr")
  4. def __getattribute__(self, name):
  5. print("getattribute")
  6. return super().__getattribute__(name)
  7. def __setattr__(self, name, value):
  8. print("setatrtr")
  9. super().__setattr__(name, value)
  10. def __delattr__(self, name):
  11. print("delattr")
  12. super().__delattr__(name)
  13. >>> c = C()
  14. >>> c.x
  15. getattribute
  16. getattr
  17. >>> c.x = 1
  18. setatrtr
  19. >>> c.x
  20. getattribute
  21. 1
  22. >>> del c.x
  23. delattr
  24. >>> c.x
  25. getattribute
  26. getattr
我們通過print() 來在調用該魔法方法的時候打印一下,這是最好的調試方式。第一次cx 的時候,對像是沒有任何屬性的,此時會先訪問getattribute,當屬性不存在時,再去訪問getattr,設置屬性時訪問setattr,刪除屬性時訪問delattr。
這幾個魔法方法在使用時,需要注意死循環陷阱。舉例說明:我們試著寫下面這個程序:
• 寫一個矩形類,默認寬和高兩個屬性
• 如果為一個叫square 的屬性賦值,那麼說明這是一個正方形,值就是正方形的邊長,此時寬和高都應該等於邊長。
  1. class Rectangle:
  2. def __init__(self, width = 0, height = 0):
  3. self.width = width
  4. self.height = height
  5. def __setattr__(self, name, value):
  6. if name = 'square':
  7. self.width = value
  8. self.height = value
  9. else:
  10. self.name = value
  11. def getArea(self):
  12. return self.width * self.height
  13. ============ RESTART: C:/Users/XiangyangDai/Desktop/上课代码/45-1.py ============
  14. >>> r = Rectangle(4, 5)
  15. Traceback (most recent call last):
  16. File "<pyshell#50>", line 1, in <module>
  17. r = Rectangle(4, 5)
  18. File "C:/Users/XiangyangDai/Desktop/上课代码/45-1.py", line 3, in __init__
  19. self.width = width
  20. File "C:/Users/XiangyangDai/Desktop/上课代码/45-1.py", line 10, in __setattr__
  21. self.name = value
  22. File "C:/Users/XiangyangDai/Desktop/上课代码/45-1.py", line 10, in __setattr__
  23. self.name = value
  24. ..........
當我們運行並初始化時,就會進入死循環,這是為什麼呢?這是因為初始化時,就會調用__setattr__魔法方法,然後就會執行self.name = value,而這個又會調用__setattr__魔法方法,然後就進入死循環了,解決方法是什麼呢?
就是把 self.name = value 這條語句改為調用基類的 __setattr__魔法方法(未被改寫的魔法方法)。如下:
  1. class Rectangle:
  2. def __init__(self, width = 0, height = 0):
  3. self.width = width
  4. self.height = height
  5. def __setattr__(self, name, value):
  6. if name == 'square':
  7. self.width = value
  8. self.height = value
  9. else:
  10. super().__setattr__(name, value)
  11. def getArea(self):
  12. return self.width * self.height
  1. >>> r = Rectangle(4, 5)
  2. >>> r.height
  3. 5
  4. >>> r.width
  5. 4
  6. >>> r.getArea()
  7. 20
  8. >>> r.square = 10
  9. >>> r.width
  10. 10
  11. >>> r.height
  12. 10
  13. >>> r.getArea()
  14. 100
除了__setattr__魔法方法,__getattribute__魔法方法也會陷入死循環的陷阱,如果一直去獲得,就會重複的獲得,死循環。推薦的解決方法就是使用基類的方法去設置、去獲得。
y.Powered by bbs.fishc.com P

0 留言:

發佈留言