2020年9月16日星期三

[課後作業] 第037講:類和對象:面向對象編程|課後測試題及答案

[課後作業] 第037講:類和對象:面向對象編程|課後測試題及答案




《零基礎入門學習Python》視頻下載地址:傳送門76uEo
E*9!yugIUf%7cie:[vZl_P;JXm<3
測試題:
rgqoWE`>MUC[#VvyH7:jn)f {}lI=
0.以下代碼體現了面向對象編程的什麼特徵?=z;Hh

  1. >>> "FishC.com".count('o')
  2. 1
  3. >>> [1, 1, 2, 3, 5, 8].count(1)
  4. 2
  5. >>> (0, 2, 4, 8, 12, 18).count(1)
  6. 0
複製代碼

if5tgI<-?:p]|jb^ws+yvD$1}
1.當程序員不想把同一段代碼寫幾次,他們發明了函數解決了這種情況。當程序員已經有了一個類,而又想建立一個非常相近的新類,他們會怎麼做呢?8_<]O4Cb
Dm{?UXi_z^a*h0~qwk7LpZQ[l68}
6Th3*8t>k p.^,7+M{_Yer
2. self參數的作用是什麼?版權屬於:bbs.fishc.com
rVC'fTB"}UmP0(+l5ezoY87dME,j
9|2k<%x;V(6c-NtjshzMLO
3.如果我們不希望對象的屬性或方法被外部直接引用,我們可以怎麼做?;m=_7C
A+YCpbW].=JhRegorV@z
*K|DH;z~TabI3)e'nE#-_ R[>?=C
4.類在實例化後哪個方法會被自動調用?uTtdF8
R3D%^-V=6clK8(P!AjX>y2SM]E
wL$:R*WEj&_1B^!aVmeX{d)pu#fY
5.請解釋下邊代碼錯誤的原因:Powered by bbs.fishc.com

  1. class MyClass:
  2.         name = 'FishC'
  3.         def myFun(self):
  4.                 print("Hello FishC!")
  5.                
  6. >>> MyClass.name
  7. 'FishC'
  8. >>> MyClass.myFun()
  9. Traceback (most recent call last):
  10.   File "<pyshell#6>", line 1, in <module>
  11.     MyClass.myFun()
  12. TypeError: myFun() missing 1 required positional argument: 'self'
  13. >>>
複製代碼

SzeVK)7-M,Qlt;u`f8A5 |_m2.:yY$
動動手:來自:bbs.fishc.com
6Xuk>g;Kec40 S$s@<`m)waH%
0.按照以下要求定義一個遊樂園門票的類,並嘗試計算2個成人+1個小孩平日票價。k7iJXd
g>t1NqW57db e}.YBca"I;`{
  • 平日票價100元
  • 週末票價為平日的120%
  • 兒童半票

BF(9d)OHAl_Vox~X1P$.m^c
1.遊戲編程:按以下要求定義一個烏龜類和魚類並嘗試編寫遊戲。(初學者不一定可以完整實現,但請務必先自己動手,你會從中學習到很多知識的^_^)Ubo^9qN
SeT1@,QF>]o"N%lIq3C#G5g29
  • 假設遊戲場景為範圍(x, y)為0<=x<=10,0<=y<=10
  • 遊戲生成1只烏龜和10條魚
  • 它們的移動方向均隨機
  • 烏龜的最大移動能力是2(Ta可以隨機選擇1還是2移動),魚兒的最大移動能力是1
  • 當移動到場景邊緣,自動向反方向移動
  • 烏龜初始化體力為100(上限)
  • 烏龜每移動一次,體力消耗1
  • 當烏龜和魚坐標重疊,烏龜吃掉魚,烏龜體力增加20
  • 魚暫不計算體力
  • 當烏龜體力值為0(掛掉)或者魚兒的數量為0遊戲結束

UXsl]E+Rg@<-Tf>K_)q6
2.請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

回复您的答案即可查看參考答案!8sKj.dbyp
W`hpq5LZx='4CtN3jK?dGi#
UJ`-CX4mv2g0_[R"Ypf@s7
測試題答案:

本帖隱藏的內容

來自:bbs.fishc.com
0.以下代碼體現了面向對象編程的什麼特徵?&gQ0*ax+
  1. >>> "FishC.com".count('o')
  2. 1
  3. >>> [1, 1, 2, 3, 5, 8].count(1)
  4. 2
  5. >>> (0, 2, 4, 8, 12, 18).count(1)
  6. 0
複製代碼

答:體現了面向對象編程的多態特徵。來自:bbs.fishc.com
W6'zOc?&i70~IBk!^dPf)MXJ>
&<p^2t|.R6nZ9{i4mT'C-)`D+]
1.當程序員不想把同一段代碼寫幾次,他們發明了函數解決了這種情況。當程序員已經有了一個類,而又想建立一個非常相近的新類,他們會怎麼做呢?(L`x?Qq=*
1Havk!wFxN%Jy>G`zs?D'Bc^rUQ9m
答:他們會定義一個新類繼承已有的這個類,這樣子就只需要簡單添加和重寫需要的方法即可。eIM7bPd
MBwhSe0,FYsgabKvkUl:md&9]@-oi
例如已有龜類,那麼如果要新定義一個甲魚類,我們只需要讓甲魚類繼承已有的龜類,然後重寫殼的屬性為“軟的”即可(據說甲魚的殼是軟的)。AJFgt
x3NKlX"yknYAdic-q6vG
BdCV'kc6&r!lNX#T)I4G~Mz%"+Y1
2. self參數的作用是什麼?7*q#jAO.
kSaiH$qL,Yt>ul }ZcN.)6Ky1'x|7"
答:綁定方法,據說有了這個參數,Python再也不會傻傻分不清是哪個對像在調用方法了,你可以認為方法中的self其實就是實例對象的唯一標誌。!-.lT&B
pZk>r]{^g28+G`$c,

3.如果我們不希望對象的屬性或方法被外部直接引用,我們可以怎麼做?D8oI<2
}p#w+nUyj"N%.;4<Idh(JR
答:我們可以在屬性或方法名字前邊加上雙下劃線,這樣子從外部是無法直接訪問到,會顯示AttributeError錯誤。$ wso._j+
  1. >>> class Person:
  2. __name = '小甲魚'
  3.         def getName(self):
  4.                 return self.__name

  5. >>> p = Person()
  6. >>> p.__name
  7. Traceback (most recent call last):
  8.   File "<pyshell#56>", line 1, in <module>
  9.     p.__name
  10. AttributeError: 'Person' object has no attribute '__name'
  11. >>> p.getName()
  12. '小甲魚'
複製代碼

我們把getName方法稱之為“訪問器”。Python事實上是採用一種叫“name mangling”技術,將以雙下劃線開頭的變量名巧妙的改了個名字而已,我們仍然可以在外部通過“_類名__變量名”的方式訪問:n %_]r#*ck
  1. >>> p._Person__name'小甲魚'
複製代碼

當然我們並不提倡這種抬槓較真粗暴不文明的訪問形式…… !3Rp-I&
a3EQ_M,nwZ?T$tqszr7k)
+owA'H;Y=s*9<5`dP(^0zt
4.類在實例化後哪個方法會被自動調用?tNZvf7dj0V
xFl%cb-z~.K^0+fav]EO$4d
答:__init__方法會在類實例化時被自動調用,我們稱之為魔法方法。你可以重寫這個方法,為對象定制初始化方案。2kA=pUf'
S^lr)!tk$snLR;dy~2Pe
G?dJ|P 'YBuw$5W.*_7,i1z
5.請解釋下邊代碼錯誤的原因:3fU>h
  1. class MyClass:
  2.         name = 'FishC'
  3.         def myFun(self):
  4.                 print("Hello FishC!")
  5.                
  6. >>> MyClass.name
  7. 'FishC'
  8. >>> MyClass.myFun()
  9. Traceback (most recent call last):
  10.   File "<pyshell#6>", line 1, in <module>
  11.     MyClass.myFun()
  12. TypeError: myFun() missing 1 required positional argument: 'self'
  13. >>>
複製代碼

答:首先你要明白類、類對象、實例對像是三個不同的名詞。3CdKL0
U,zQ`rTY27m4E3|wfkCFJGu-
我們常說的類指的是類定義,由於“Python無處不對象”,所以當類定義完之後,自然就是類對象。在這個時候,你可以對類的屬性(變量)進行直接訪問(MyClass.name)。fVTR"
p"R>4_9#]3`k;5~Sx+sgnjGNh
一個類可以實例化出無數的對象(實例對象),Python為了區分是哪個實例對象調用了方法,於是要求方法必須綁定(通過self參數)才能調用。而未實例化的類對象直接調用方法,因為缺少self參數,所以就會報錯。A%]1k+

來自:bbs.fishc.com
動動手答案:

本帖隱藏的內容

來自:bbs.fishc.com
0.按照以下要求定義一個遊樂園門票的類,並嘗試計算2個成人+1個小孩平日票價。,D-hfs
q 4p<{FYi^R'yk`%j]?Eah
  • 平日票價100元
  • 週末票價為平日的120%
  • 兒童半票

答:面向對象編程的難點在於思維的轉換。版權屬於:bbs.fishc.com
j<f=+V6[$SXl&@pbGg;^e9a1vcdnZ
代碼清單:來自:bbs.fishc.com
  1. class Ticket():
  2.         def __init__(self, weekend=False, child=False):
  3.                 self.exp = 100
  4.                 if weekend:
  5.                         self.inc = 1.2
  6.                 else:
  7.                         self.inc = 1
  8.                 if child:
  9.                         self.discount = 0.5
  10.                 else:
  11.                         self.discount = 1
  12.         def calcPrice(self, num):
  13.                 return self.exp * self.inc * self.discount * num

  14. >>> adult = Ticket()
  15. >>> child = Ticket(child=True)
  16. >>> print("2個成人+ 1個小孩平日票價為:%.2f" % (adult.calcPrice(2) + child.calcPrice(1)))
  17. 2個成人+ 1個小孩平日票價為:250.00
複製代碼

"B<(|t*9;Cd`QLbIE=83KiZW?MpN2
1.遊戲編程:按以下要求定義一個烏龜類和魚類並嘗試編寫遊戲。(初學者不一定可以完整實現,但請務必先自己動手,你會從中學習到很多知識的^_^)qOUdQsH
O4wY6:=cn7AVNs_5kd?,o-
  • 假設遊戲場景為範圍(x, y)為0<=x<=10,0<=y<=10
  • 遊戲生成1只烏龜和10條魚
  • 它們的移動方向均隨機
  • 烏龜的最大移動能力是2(Ta可以隨機選擇1還是2移動),魚兒的最大移動能力是1
  • 當移動到場景邊緣,自動向反方向移動
  • 烏龜初始化體力為100(上限)
  • 烏龜每移動一次,體力消耗1
  • 當烏龜和魚坐標重疊,烏龜吃掉魚,烏龜體力增加20
  • 魚暫不計算體力
  • 當烏龜體力值為0(掛掉)或者魚兒的數量為0遊戲結束

答:參考代碼附詳細註釋,希望先自己認真完成,你會從中學習到很多知識的。*i9TV)!Y
`BqYF{Ijctu@ +95ymw;?K,
代碼清單:版權屬於:bbs.fishc.com
  1. import random as r

  2. legal_x = [0, 10]
  3. legal_y = [0, 10]

  4. class Turtle:
  5.     def __init__(self):
  6.         # 初始體力
  7.         self.power = 100
  8.         # 初始位置隨機
  9.         self.x = r.randint(legal_x[0], legal_x[1])
  10.         self.y = r.randint(legal_y[0], legal_y[1])

  11.     def move(self):
  12.         # 隨機計算方向並移動到新的位置(x, y)
  13.         new_x = self.x + r.choice([1, 2, -1, -2])
  14.         new_y = self.y + r.choice([1, 2, -1, -2])
  15.         # 檢查移動後是否超出場景x軸邊界
  16.         if new_x < legal_x[0]:
  17.             self.x = legal_x[0] - (new_x - legal_x[0])
  18.         elif new_x > legal_x[1]:
  19.             self.x = legal_x[1] - (new_x - legal_x[1])
  20.         else:
  21.             self.x = new_x
  22.         # 檢查移動後是否超出場景y軸邊界
  23.         if new_y < legal_y[0]:
  24.             self.y = legal_y[0] - (new_y - legal_y[0])
  25.         elif new_y > legal_y[1]:
  26.             self.y = legal_y[1] - (new_y - legal_y[1])
  27.         else:
  28.             self.y = new_y        
  29.         # 體力消耗
  30.         self.power -= 1
  31.         # 返回移動後的新位置
  32.         return (self.x, self.y)

  33.     def eat(self):
  34.         self.power += 20
  35.         if self.power > 100:
  36.             self.power = 100

  37. class Fish:
  38.     def __init__(self):
  39.         self.x = r.randint(legal_x[0], legal_x[1])
  40.         self.y = r.randint(legal_y[0], legal_y[1])
  41.         
  42.     def move(self):
  43.         # 隨機計算方向並移動到新的位置(x, y)
  44.         new_x = self.x + r.choice([1, -1])
  45.         new_y = self.y + r.choice([1, -1])
  46.         # 檢查移動後是否超出場景x軸邊界
  47.         if new_x < legal_x[0]:
  48.             self.x = legal_x[0] - (new_x - legal_x[0])
  49.         elif new_x > legal_x[1]:
  50.             self.x = legal_x[1] - (new_x - legal_x[1])
  51.         else:
  52.             self.x = new_x
  53.         # 檢查移動後是否超出場景y軸邊界
  54.         if new_y < legal_y[0]:
  55.             self.y = legal_y[0] - (new_y - legal_y[0])
  56.         elif new_y > legal_y[1]:
  57.             self.y = legal_y[1] - (new_y - legal_y[1])
  58.         else:
  59.             self.y = new_y
  60.         # 返回移動後的新位置
  61.         return (self.x, self.y)

  62. turtle = Turtle()
  63. fish = []
  64. for i in range(10):
  65.     new_fish = Fish()
  66.     fish.append(new_fish)

  67. while True:
  68.     if not len(fish):
  69.         print("魚兒都吃完了,遊戲結束!")
  70.         break
  71.     if not turtle.power:
  72.         print("烏龜體力耗盡,掛掉了!")
  73.         break

  74.     pos = turtle.move()
  75.     # 在迭代器中刪除列表元素是非常危險的,經常會出現意想不到的問題,因為迭代器是直接引用列表的數據進行引用
  76.     # 這裡我們把列表拷貝給迭代器,然後對原列表進行刪除操作就不會有問題了^_^
  77.     for each_fish in fish[:]:
  78.         if each_fish.move() == pos:
  79.             # 魚兒被吃掉了
  80.             turtle.eat()
  81.             fish.remove(each_fish)
  82.             print("有一條魚兒被吃掉了...")
複製代碼

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

小甲魚希望你認真對待作業就像你希望小甲魚推出高質量視頻一樣渴望^_^
經過上一節課的熱身,相信大家對類和對像已經有了初步的認識。這節課通過幾個主題來理解,面向對象編程。
(一)self 是什麼?
Python 的self 相當於C++ 的this 指針。我們知道,類是圖紙,而由類實例化出的對象才是真正可以住人的房子,這是我們上節課大的比方。我們還知道,根據一張圖紙就可以設計出成千上萬的房子,這些房子都長得差不多,因為它們都來自於同一張圖紙,但是它們都有不同的主人,每個人都只可以回到自己的家,self 就相當於每個房子的門牌號,有了self,就可以輕鬆的找到自己的房子,Python 的self 參數就是同樣的道理,由同一個類可以生成無數個對象,這些對像都長得很相似,因為它們都是來源於同一個類的屬性和方法,當一個對象的方法被調用的時候,對象會將自身作為第一個參數傳給self 參數,接收到這個self 參數的時候,Python 就知道你是哪一個對像在調用方法了。舉例說明:
  1. >>> class Ball:
  2. def setName(self, name):
  3. self.name = name
  4. def kick(self):
  5. print("我叫%s,该死的,谁踢我..."% self.name)
  6. >>> a = Ball()
  7. >>> a.setName("球A")
  8. >>> b = Ball()
  9. >>> b.setName("球B")
  10. >>> a.kick()
  11. 我叫球A,该死的,谁踢我...
  12. >>> b.kick()
  13. 我叫球B,该死的,谁踢我...
我們發現,在對像類定義這裡,它的這些方法都有一個self,我們生成兩個實例化對象a 和b,這裡都調用kick() 方法,但是實現結果不一樣,是因為a.kick()和b.kick() 都有一個隱藏屬性self,會找到各自對應的name,這些都是由Python 在背後默默的工作,你只需要在類的定義的時候把self 寫進第一個參數。
(二)Python 的魔法方法
你聽說過Python 的魔法方法嗎?據說,Python 的對像天生擁有一些神奇的方法,它們是面向對象的Python的一切......它們是可以給你的類增加魔力的特殊方法......如果你的對象實現了這些方法中的某一個,那麼這個方法在特殊情況下被Python所調用,而這一切都是自動發生的......Python的這些具有魔力的方法總是會被雙下劃線所包圍,我們今天介紹其中一個最基礎的特殊方法:__init__(self)
對於Python的其它的魔法方法,我們後面會專門進行講解。我們把__init__(self)方法稱為構造方法,__init__(self)方法的魔力體現在只要實例化一個對象的時候,那麼這個方法就會在對像被創建的時候自動調用。有過C++基礎的同學就會知道,這就是構造函數。
其實實例化對象的時候是可以存入參數的,這些參數會自動的存入到__init__(self)方法中,
__init__(self,param1,param2...) #(默認不重寫的形式就是__init__(self))
也就是我們這個魔法方法中,我們可以通過重寫這個方法(如上)來自定義對象的初始化操作,說起來比較複雜,舉例說明:
  1. >>> class Ball:
  2. def __init__(self, name):
  3. self.name = name
  4. def kick(self):
  5. print("我叫%s,该死的,谁踢我..."% self.name)
  6. >>> a = Ball("土豆") #因为重写了__init__(self)方法,实例化对象时需要一个参数
  7. >>> a.kick()
  8. 我叫土豆,该死的,谁踢我...
  9. >>> b = Ball() #这里没有传入参数就会报错,可以在定义类是给name设置默认参数
  10. Traceback (most recent call last):
  11. File "<pyshell#84>", line 1, in <module>
  12. b = Ball()
  13. TypeError: __init__() missing 1 required positional argument: 'name'
(三)公有和私有
默認上來說,對象的屬性和方法都是公開的,都是共有的,我們可以通過點(.)操作符來進行訪問,舉例說明:
  1. >>> class Person:
  2. name = "来自江南的你"
  3. >>> p = Person()
  4. >>> p.name
  5. '来自江南的你'
為了實現類似於私有變量的特徵,Python內部採用了一種叫做name mangling(名字改編,名字重整)的技術,在Python 中定義私有變量只主要在變量名或函數名前加上“__”兩個下劃線,那麼這個函數或變量就會為私有的了。
  1. >>> class Person:
  2. __name = "来自江南的你"
  3. >>> p = Person()
  4. >>> p.__name
  5. Traceback (most recent call last):
  6. File "<pyshell#94>", line 1, in <module>
  7. p.__name
  8. AttributeError: 'Person' object has no attribute '__name'
  9. >>> p.name
  10. Traceback (most recent call last):
  11. File "<pyshell#95>", line 1, in <module>
  12. p.name
  13. AttributeError: 'Person' object has no attribute 'name'
這時,p.__name 和p.name 都無法訪問對象的name屬性,因為它們都找不到了,這樣在外部就會將變量名隱藏起來,理論上如果要訪問,就要從內部進行,可以這樣寫:
  1. >>> class Person:
  2. __name = "来自江南的你"
  3. def getName(self):
  4. return self.__name
  5. >>> p = Person()
  6. >>> p.getName()
  7. '来自江南的你'
上面的方法只是理論上的,其實只要你琢磨一下,name mangling技術的意思就是名字改編、名字重整,那麼應該不難發現,Python只是動了一下手腳,它把雙下劃線開頭的變量改了名字而已,它自動是改成了  _類名__變量名(單下劃線+類名+雙下劃線+變量名),如下:
  1. >>> class Person:
  2. __name = "来自江南的你"
  3. >>> p = Person()
  4. >>> p._Person__name
  5. '来自江南的你'
 所以說,Python的私有機制是偽私有,Python是沒有權限控制的,所以變量是可以被外部調用的。

0 留言:

發佈留言