本篇博客是博主自己在這里:https://github.com/jackfrued/Python-100-Days 學習Python時做的一些筆記,由于我已經有了一些基礎(因為學習過C語言、Java等,其中涉及到的有比如多線程、GUI、網絡編程等內容),所以這里做的筆記可能對于新手來說比較有跳躍性。如果你確實有這樣的體會,那建議去找其他路徑來學習。買過一本關于Python爬蟲的書,附了張學習Python的學習路線圖,分享給大家下,僅供想要學習Python的同學參考:
本篇包括:
- 語言設計基礎
- 面向對象編程
- 圖形用戶界面和游戲開發(fā)
一、語言設計基礎
1、注釋
單行注釋 - 以#和空格開頭的部分
多行注釋 - 三個引號開頭,三個引號結尾
2、運算符
a = 5
print("a = ", a)
flag1 = 3 > 2
print("flag1 = ", flag1)
#!/usr/bin/python
# -*- coding: latin-1 -*-
import os, sys
f = float(input('請輸入華氏溫度:'))
c = (f - 32) / 1.8
print('%.1f華氏度 = %.1f攝氏度' % (f, c))
#!/usr/bin/python
# -*- coding: latin-1 -*-
import os, sys, math
radius = float(input('Please input redius of the cicle: '))
perimeter = 2 * math.pi * radius
area = math.pi * radius * radius
print('Perimeter = %.2f' % perimeter)
print('Area = %.2f' % area)
# 輸入年份 如果是閏年輸出True 否則輸出False
year = int(input('Please input a year: '))
# 如果代碼太長寫成一行不便于閱讀 可以使用\或()折行
is_leap = (year % 4 == 0 and year % 100 != 0 or
year % 400 == 0)
print(is_leap)
3、分支結構
- 和C/C++、Java等語言不同,Python中if…else… 沒有用花括號 來構造代碼塊,而是使用了縮進的方式來設置代碼的層次結構。如果if條件成立的情況下需要執(zhí)行多條語句,只要保持多條語句具有相同的縮進就可以了;換句話說連續(xù)的代碼如果又保持了 相同的縮進 那么它們屬于同一個代碼塊,相當于是一個執(zhí)行的整體。
示例5-1
# 用戶身份驗證
username = input('Input Username: ')
password = input('Input Password: ')
# 如果希望輸入Password時 終端中沒有回顯 可以使用getpass模塊的getpass函數(shù)
# import getpass
# password = getpass.getpass('Input Password: ')
if username == 'admin' and password == '123456':
print('Success!')
else:
print('Fault!')
2. 如果要構造出更多的分支,則使用if…elif…else…結構。
示例5-2
"""
分段函數(shù)求值:
3x - 5 (x > 1)
f(x) = x + 2 (-1 <= x <= 1)
5x + 3 (x < -1)
"""
x = float(input('x = '))
if x > 1:
y = 3 * x - 5
elif x >= -1:
y = x + 2
else:
y = 5 * x + 3
print('f(%.2f) = %.2f' % (x, y))
示例6
# 擲骰子決定做什么事情
from random import randint
face = randint(1, 6) #使用random模塊的randint函數(shù)生成指定范圍的隨機數(shù)來模擬擲骰子
if face == 1:
result = 'Sing'
elif face == 2:
result = 'Dance'
elif face == 3:
result = 'Paint'
elif face == 4:
result = 'Socccer'
elif face == 5:
result = 'Tennis'
else:
result = 'Cold Joke'
print(result)
4、循環(huán)結構
- Python中構造循環(huán)結構有兩種做法,一種是for-in循環(huán),一種是while循環(huán):如果明確的知道循環(huán)執(zhí)行的次數(shù)或者要對一個容器進行迭代(后面會講到),那么我們推薦使用for-in循環(huán);否則,推薦使用while循環(huán)。
示例7
# 用for循環(huán)實現(xiàn)1~100求和
sum = 0
for x in range(101):
sum += x
print(sum)
"""
range可以用來產生一個不變的數(shù)值序列,而且這個序列通常都是用在循環(huán)中的,例如:
range(101)可以產生一個0到100的整數(shù)序列。
range(1, 100)可以產生一個1到99的整數(shù)序列。
range(1, 100, 2)可以產生一個1到99的奇數(shù)序列,其中的2是步長,即數(shù)值序列的增量——
此結構可以實現(xiàn)功能:1~100之間的偶數(shù)求和——
只需將range(101)替換成range(2, 101, 2)即可。
"""
示例8
"""
猜數(shù)字游戲
計算機出一個1~100之間的隨機數(shù)由人來猜
計算機根據人猜的數(shù)字分別給出提示大一點Larger/小一點Smaller/猜對了
"""
import random
answer = random.randint(1, 100)
counter = 0
while True:
counter += 1
number = int(input('Input a guess number: '))
if number < answer:
print('Larger')
elif number > answer:
print('Smaller')
else:
print('Guess Right!')
break
print('You guessed %d times.' % counter)
循環(huán)結構_練習
示例9
# 輸入一個正整數(shù)判斷它是不是素數(shù)
from math import sqrt
num = int(input('請輸入一個正整數(shù): '))
end = int(sqrt(num))
is_prime = True
for x in range(2, end + 1):
if num % x == 0:
is_prime = False
break
if is_prime and num != 1:
print('%d是素數(shù)' % num)
else:
print('%d不是素數(shù)' % num)
示例9
- 我的解法:
# 輸入兩個正整數(shù),計算最大公約數(shù)和最小公倍數(shù)
# 注意最大公約數(shù)與最小公倍數(shù)的關系!!!
a = int(input("請輸入一個正整數(shù):"))
b = int(input("請再輸入一個正整數(shù):"))
if a >= b:
min = b
else:
min = a
while(min != 1):
if(a % min == 0 and b % min == 0):
print('%d和%d的最大公約數(shù)是:%d' % (a, b, min))
print('%d和%d的最小公倍數(shù)是:%d' % (a, b, a * b // min))
break
else:
min -= 1
- 老師解法:
# 輸入兩個正整數(shù),計算最大公約數(shù)和最小公倍數(shù)
x = int(input('x = '))
y = int(input('y = '))
if x > y:
x, y = y, x # 啥意思???
for factor in range(x, 0, -1):
if x % factor == 0 and y % factor == 0:
print('%d和%d的最大公約數(shù)是%d' % (x, y, factor))
print('%d和%d的最小公倍數(shù)是%d' % (x, y, x * y // factor))
break
示例10
"""
Craps賭博游戲:
玩家搖兩顆色子,如果第一次搖出的兩篩子之和為7點或11點——玩家勝;
如果搖出2點 3點 12點——莊家勝;
其他情況時,則需要玩家再次搖這兩顆色子:如果和為7點——莊家勝;
如果和與第一次相同——玩家勝。
玩家進入游戲時有1000元的資本,玩家每次選擇賭注,直至全部輸光,游戲結束。
"""
from random import randint
money = 1000 # 玩家的總資產...不需要在意莊家的,始終是玩家的總資產在改變著
while money > 0:
print('你的總資產為:%d' % money)
needs_go_on = False
while True:
debt = int(input('請下注:'))
if debt > 0 and debt <= money:
break
first = randint(1, 6) + randint(1, 6)
print('玩家搖出了%d點' % first)
if first == 7 or first == 11:
print('Player Success')
money += debt
elif first == 2 or first == 3 or first == 12:
print('Zhuangjia Success')
money -= debt
else: # 搖出的兩篩子之和不是7/11/22/3/12的情況
needs_go_on = True
while needs_go_on:
current = randint(1, 6) + randint(1, 6)
print('玩家搖出了%d點' % current)
if current == 7:
print('Zhuangjia Success')
money -= debt
needs_go_on = False
elif current == first:
print('Player Success')
money += debt
needs_go_on = False
print('你破產了...Game Over! ')
示例11
# 輸出斐波那契數(shù)列的前20個數(shù)
a = 0
b = 1
for _ in range(20):
a, b = b, a + b
print(a)
- 講解:Python中的賦值語法——從右往左進行計算,然后再依次(從左往右)賦值
1、a, b = b, a + b 等價于:a = b, b = a + b。過程:
temp = b # 先保存b的原值
b = a + b # 賦b新值
a = temp # 將b的原值賦予a
2、a, b = 1, 3 等價于 a = 1, b = 3
示例12
# 判斷輸入的正整數(shù)是不是回文數(shù)
num = int(input('請輸入一個正整數(shù): '))
temp = num
num2 = 0
while temp > 0:
num2 *= 10 # 每次擴大十倍,為下面加上“個位”做準備
num2 += temp % 10 # 每次得到temp的最低位
temp //= 10 # 每次得到舍去temp最低位后的那個數(shù)
if num == num2:
print('%d是回文數(shù)' % num)
else:
print('%d不是回文數(shù)' % num)
示例13
"""
找出1~9999之間的所有完美數(shù)
完美數(shù)是除自身外其他所有因子的和正好等于這個數(shù)本身的數(shù)
例如: 6 = 1 + 2 + 3, 28 = 1 + 2 + 4 + 7 + 14
"""
for i in (1, 10000):
sum = 0
for factor in range(1, int(math.sqrt(num)) + 1):
if num % factor == 0:
sum += fator;
# 如28可以被2整除,那么其結果14也必定是28的因子。這里就是這樣找出它的“另一半”的
if factor > 1 and num / factor != factor:
sum += num / factor
if sum == num:
print(num)
5、函數(shù)
函數(shù)的定義
Python中的函數(shù)與其他語言中的函數(shù)有很多不太相同的地方,其中一個顯著的區(qū)別就是Python對函數(shù)參數(shù)的處理。在Python中,函數(shù)的參數(shù)可以有 默認值 ,也支持使用 可變參數(shù) ,所以Python并不需要像其他語言一樣支持函數(shù)的重載,因為我們在定義一個函數(shù)的時候可以讓它有多種不同的使用方式,下面是兩個小例子。
- 為參數(shù)設定默認值
from random import randint
"""
搖色子
:param n: 色子的個數(shù)
:return: n顆色子點數(shù)之和
"""
def roll_dice(n=2):
total = 0
for _ in range(n):
total += randint(1, 6)
return total
def add(a=0, b=0, c=0):
return a + b + c
# 如果沒有指定參數(shù)那么使用默認值搖兩顆色子
print(roll_dice())
# 搖三顆色子
print(roll_dice(3))
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
# 傳遞參數(shù)時可以不按照設定的順序進行傳遞
print(add(c=50, a=100, b=200))
- 設置可變參數(shù)
# 在參數(shù)名前面的*表示args是一個可變參數(shù)
# 即在調用add函數(shù)時可以傳入0個或多個參數(shù)
def add(*args):
total = 0
for val in args:
total += val
return total
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))
函數(shù)的管理——用模塊管理函數(shù)
- 由于Python沒有函數(shù)重載的概念,那么后面的函數(shù)定義會覆蓋之前的函數(shù)定義,也就意味著兩個函數(shù)同名函數(shù)實際上只有一個是存在的。那么如何使得兩個同名函數(shù)都能夠互不干擾的存在呢?答案其實很簡單, Python中每個文件就代表了一個模塊(module),在不同的模塊中可以有同名的函數(shù),在使用函數(shù)的時候我們通過 import 關鍵字導入指定的模塊就可以區(qū)分到底要使用的是哪個模塊中的foo函數(shù) ,代碼如下所示。
module1.py
def foo():
print('hello, world!')
module2.py
def foo():
print('goodbye, world!')
方式1
test.py
from module1 import foo
foo() # 輸出hello, world!
from module2 import foo
foo() # 輸出goodbye, world!
方式2
test.py
import module1 as m1
import module2 as m2
m1.foo()
m2.foo()
錯誤示范:后導入的foo覆蓋了之前導入的foo
from module1 import foo
from module2 import foo
# 輸出goodbye, world!
foo()
- 需要說明的是,如果我們導入的模塊除了定義函數(shù)之外還中有可以執(zhí)行代碼,那么Python解釋器在導入這個模塊時就會執(zhí)行這些代碼,事實上我們可能并不希望如此,因此如果我們在模塊中編寫了可執(zhí)行代碼,最好是將這些執(zhí)行代碼放入如下所示的條件中,這樣的話除非直接運行該模塊,否則if條件下的這些代碼(函數(shù)中的可執(zhí)行代碼)是不會執(zhí)行的,因為只有 直接執(zhí)行的模塊 (或者說是“ 當前正在執(zhí)行的模塊 ”)的名字才是“ main ”。
module3.py
def foo():
pass
def bar():
pass
# __name__是Python中一個隱含的變量,它代表模塊的名字
# 只有被Python解釋器直接執(zhí)行的模塊的名字才是__main__
if __name__ == '__main__':
print('call foo()')
foo()
print('call bar()')
bar()
test.py
import module3
# 導入module3時,不會執(zhí)行模塊中if條件成立時的代碼,因為if中模塊的名字_name_是module3而不是__main__,所以不符合if執(zhí)行的條件。所以我們導入的module3模塊中的可執(zhí)行代碼不會執(zhí)行。
- Python中有關變量作用域的問題的討論具體請參考這里。
def foo():
b = 'hello'
def bar(): # Python中可以在函數(shù)內部再定義函數(shù)
c = True
print(a)
print(b)
print(c)
bar()
# print(c) # NameError: name 'c' is not defined
if __name__ == '__main__':
a = 100
# print(b) # NameError: name 'b' is not defined
foo()
說明:輸出為100 hello True。原因請點擊上面的鏈接進行查看(之所以沒有解釋,是因為自己會,只是為了以防萬一又不會,所以還是簡單的放在這里以下)。
- 從現(xiàn)在開始我們可以將Python代碼按照下面的格式進行書寫,這一點點的改進其實就是在我們理解了函數(shù)和作用域的基礎上跨出的巨大的一步。
def main():
# Todo: Add your code here
pass # pass寫在任何縮進的語句塊部分,只是占位,什么事情都不做。為了滿足python的語法要求。
if __name__ == '__main__':
main()
6、常用數(shù)據結構-練習題
Click Here.
二、面向對象編程
1、基礎
類是對象的藍圖和模板,而對象是類的實例。可以看出,類是抽象的概念,而對象是具體的東西。在面向對象編程的世界中,一切皆為對象,對象都有屬性和行為,每個對象都是獨一無二的,而且對象一定屬于某個類(型)。當我們把一大堆擁有共同特征的對象的靜態(tài)特征(屬性——通過類中的變量描述)和動態(tài)特征(行為——通過類中的方法描述【 寫在類中的函數(shù),我們通常稱之為(對象的)方法】)都抽取出來后,就可以定義出一個叫做“類”的東西。
類的定義、創(chuàng)建、使用
務必仔細閱讀下面的程序及注釋,描述了相關的語法規(guī)則。更詳細的Python中類的定義的解釋,請看這里。
class Student(object):
# __init__是一個特殊方法用于在創(chuàng)建對象時進行初始化操作
# 通過這個方法我們可以為學生對象綁定name和age兩個屬性
def __init__(self, name, age): # self參數(shù)是類中的每個方法的參數(shù)都必須有的,用于指示本對象,相當于Java中的this
self.name = name
self.age = age
def study(self, course_name):
print('%s正在學習%s.' % (self.name, course_name))
# PEP 8要求標識符的名字用全小寫多個單詞用下劃線連接
# 但是部分程序員和公司更傾向于使用駝峰命名法(駝峰標識)
def watch_movie(self):
if self.age < 18:
print('%s只能看《熊出沒》.' % self.name)
else:
print('%s正在看喜歡的電影.' % self.name)
def main():
stu1 = Student('zjy', 18) # 自動調用__init__方法,使‘Student’賦值給self,‘zjy’賦值給name,'18'賦值給age
stu1.study('Python程序設計')
stu1.watch_movie()
if __name__ == '__main__':
main()
類中屬性/方法的訪問權限
在Python中,屬性和方法的訪問權限只有兩種,也就是 公開 的和 私有 的,如果希望屬性是私有的,在給屬性命名時可以用 兩個下劃線作為開頭 ,下面的代碼可以驗證這一點。
class Test:
def __init__(self, foo):
self.__foo = foo # 左側的__foo是類中的屬性,foo只是參數(shù)罷了
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello') # 自動調用__init__方法,使‘Test’賦值給self,‘hello’賦值給foo
# (私有方法)AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# (私有屬性)AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()
但是,Python并沒有從語法上嚴格保證私有屬性或方法的私密性,它只是給私有的屬性和方法換了一個名字來“妨礙”對它們的訪問,事實上如果你知道更換名字的規(guī)則,那么你仍然可以訪問到那些所謂的私有屬性。下面的代碼就可以驗證這一點,并且你可以從中知道“ 更換名字的規(guī)則 ”。之所以這樣設定,可以用這樣一句名言加以解釋,就是“We are all consenting adults here”。因為絕大多數(shù)程序員都認為開放比封閉要好,而且程序員要自己為自己的行為負責。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar() # 使私有的__bar()方法變成“公有”
print(test._Test__foo) # 使私有的__foo屬性變成“公有”
if __name__ == "__main__":
main()
在實際開發(fā)中,我們并不建議將屬性設置為私有的,因為這會導致子類無法訪問(后面會講到)。所以大多數(shù)Python程序員會遵循一種命名慣例就是讓屬性名以單下劃線開頭來表示屬性是受保護的,本類之外的代碼在訪問這樣的屬性時應該要保持慎重。這種做法并不是語法上的規(guī)則,因為 單下劃線開頭 的屬性和方法外界仍然是可以訪問的,所以更多的時候它是一種暗示或隱喻:“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
- 練習1:定義一個類描述數(shù)字時鐘
from time import sleep
class Clock(object):
def __init__(self, hour=0, minute=0, second=0):
# 成員變量hour、minute、second是“受保護”的——名稱以單個下劃線開始
self._hour = hour
self._minute = minute
self._second = second
def run(self):
self._second += 1
if self._second == 60:
self._second = 0;
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
def main():
clock = Clock(23, 59, 58)
while True:
print(clock.show())
sleep(1)
clock.run()
- 練習2:定義一個類描述平面上的點并提供移動點和計算到另一個點距離的方法
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def show(self):
print('點在平面上的位置:x = %d, y = %d' % (self.x, self.y))
def distance(self, other):
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)
def move_to(self, x, y):
"""移動到指定位置
:param x: 新的橫坐標
"param y: 新的縱坐標
"""
self.x = x
self.y = y
def move_by(self, dx, dy):
"""移動指定的增量
:param dx: 橫坐標的增量
"param dy: 縱坐標的增量
"""
self.x += dx
self.y += dy
def main():
p1 = Point(3, 5)
p2 = Point()
p1.show()
p2.show()
p2.move_by(-1, 2)
p2.show()
print(p1.distance(p2))
if __name__ == '__main__':
main()
面向對象的三大支柱 之 封裝
- “隱藏一切可以隱藏的實現(xiàn)細節(jié),只向外界暴露(提供)簡單的編程接口”。我們在類中定義的方法其實就是把數(shù)據和對數(shù)據的操作封裝起來了,在我們創(chuàng)建了對象之后,只需要給對象發(fā)送一個消息(調用方法)就可以執(zhí)行方法中的代碼,也就是說我們只需要知道方法的名字和傳入的參數(shù)(方法的外部視圖),而不需要知道方法內部的實現(xiàn)細節(jié)(方法的內部視圖)。
2、進階
@property裝飾器
以上,我們知道了: 不建議將屬性設置為私有的,但是如果直接將屬性暴露給外界也是有問題的 ,比如我們沒有辦法檢查賦給屬性的值是否有效。我們之前的建議是將屬性命名 以單下劃線開頭 ,通過這種方式來暗示屬性是受保護的,不建議外界直接訪問,那么如果想訪問屬性可以通過屬性的 getter(訪問器) 和 setter(修改器) 方法進行對應的操作。如果要做到這點,就可以考慮使用 @property包裝器 來包裝getter和setter方法,使得對屬性的訪問既安全又方便,代碼如下所示。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 必須要先getter方法,然后再setter方法,否則會有編譯錯誤
@property # 訪問器:getter方法
def age(self):
return self._age
@property # 訪問器:getter方法
def name(self):
return self._name
@age.setter # 修改器:setter方法
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飛行棋...' % self._name)
else:
print('%s正在玩斗地主...' % self._name)
def main():
person = Person('zjy', 12)
person.play()
"""
為了重新為person._age賦值,系統(tǒng)根據此語句將自動調用相應的set方法,重新為_age賦值。
這里使用的是_age,實際上,使用person.age也ok,
但其他age的變形就不行了,也就是說,可以是_age或age,
看下一知識點就可以知道如何進行名稱使用的限制了
"""
person._age = 22
"""
你以為下面也是為變量重新賦值的語句,但其實:TypeError: 'int' object is not callable.
所以,這是一種錯誤的重新為成員變量賦值的方法,與Java不太相同的地方。
Java要寫出調用的set方法為變量賦值,
而這里的重新賦值應該寫成上面那樣,系統(tǒng)明白(@age.setter)你是想重新為變量賦值。
"""
# person.age(22)
person.play()
"""
下面的語句:AttributeError: can't set attribute.
因為沒有提供name的set方法(@name.setter),所以不能修改此屬性。
"""
# person.name = 'zyy'
"""
如何通過所謂的get方法調用屬性值?這里有必要說一下:
你以為:print('姓名:%c' % person.name())
但實際:print('姓名:%c' % person.name)
又與Java不同,這里不把他們當做方法來使用,而是直接當作成員變量!
然后會自動調用相應的get方法~
"""
print('姓名:%s' % person.name)
if __name__ == '__main__':
main()
__slots__魔法
至此,你應該知道Python是一門動態(tài)語言。通常,動態(tài)語言允許我們在程序運行時給對象綁定新的屬性或方法,當然也可以對已經綁定的屬性和方法進行解綁定。但是如果我們需要限定自定義類型的 對象只能綁定指定的某些屬性 ,可以通過在類中定義__slots__變量來進行限定。需要注意的是 __slots__的限定只對當前類的對象生效,對子類并不起任何作用 。
class Person(object):
# 限定Person對象只能綁定_name,_age和_gender屬性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飛行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('zjy', 22)
person.play()
person._gender = '男' # 這回就不可以用person.gender了!因為屬性名已經被限制了!
if __name__ == '__main__':
main()
@staticmethod 與 @classmethod
在此之前,我們在類中定義的方法都稱為對象方法,也就是說這些方法都是發(fā)送給 對象 的消息。下面,我們來學習兩種非寫給對象的方法,而是屬于類的方法。
@staticmethod
使用@staticmethod定義出來的方法叫做 靜態(tài)方法 。例如我們定義一個“三角形”類,通過傳入三條邊長來構造三角形,并提供計算周長和面積的方法,但是傳入的三條邊長未必能構造出三角形對象,因此我們可以先寫一個方法來驗證三條邊長是否可以構成三角形,這個方法很顯然就不是對象方法,因為在調用這個方法時三角形對象尚未創(chuàng)建出來(因為都不知道三條邊能不能構成三角形),所以這個方法是屬于三角形類而并不屬于三角形對象的。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
# 求三角形周長
def perimeter(self):
return self._a + self._b + self._c
# 求三角形面積
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 靜態(tài)方法(以及下面要講的類方法)都是通過給類發(fā)消息來調用的
if Triangle.is_valid(a, b, c): # 關鍵語句!!!
t = Triangle(a, b, c)
# 調用對象方法的第一種方式
print(t.perimeter())
# 調用對象方法的第二種方式:通過給類發(fā)消息,此時需要傳入接收消息的類對象作為參數(shù)
# print(Triangle.perimeter(t))
print(t.area()) # 等價于:print(Triangle.area(t))
else:
print('無法構成三角形...')
if __name__ == '__main__':
main()
@classmethod
和靜態(tài)方法比較類似,Python還可以在類中定義類方法, 類方法的第一個參數(shù)約定名為cls ,它代表的是與當前類相關的信息的對象(類本身也是一個對象,有的地方也稱之為 類的元數(shù)據對象 ),通過這個參數(shù)我們可以獲取和類相關的信息并且通過此創(chuàng)建出類的對象,代碼如下所示。
from time import time, localtime, sleep
class Clock(object):
"""數(shù)字時鐘"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
def now(cls):
ctime = localtime(time()) # 調用Python內置函數(shù)localtime()、time()
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""顯示時間"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
clock = Clock.now() # 關鍵語句——通過類方法創(chuàng)建對象并獲取系統(tǒng)時間
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
類之間的關系
簡單的說,類和類之間的關系有三種:is-a、has-a和use-a關系:
- is-a 關系也叫繼承或泛化,比如學生和人的關系、手機和電子產品的關系都屬于繼承關系。
- has-a 關系通常稱之為關聯(lián),比如部門和員工的關系,汽車和引擎的關系都屬于關聯(lián)關系;關聯(lián)關系如果是整體和部分的關聯(lián),那么我們稱之為聚合關系;如果整體進一步負責了部分的生命周期(整體和部分是不可分割的,同時同在也同時消亡),那么這種就是最強的關聯(lián)關系,我們稱之為合成關系。
- use-a 關系通常稱之為依賴,比如司機有一個駕駛的行為(方法),其中(的參數(shù))使用到了汽車,那么司機和汽車的關系就是依賴關系。
我們可以使用一種叫做UML(統(tǒng)一建模語言)的東西來進行面向對象建模,其中一項重要的工作就是把類和類之間的關系用標準化的圖形符號描述出來。關于UML我們在這里不做詳細的介紹,有興趣的讀者可以自行閱讀《UML面向對象設計基礎》一書。
利用類之間的這些關系,我們可以在已有類的基礎上來完成某些操作,也可以在已有類的基礎上創(chuàng)建新的類,這些都是實現(xiàn)
代碼復用
的重要手段。復用現(xiàn)有的代碼不僅可以減少開發(fā)的工作量,也有利于代碼的管理和維護,這是我們在日常工作中都會使用到的技術手段。
面向對象的三大支柱 之 繼承
上面已提到:可以在已有類的基礎上創(chuàng)建新類。這其中的一種做法就是讓一個類從另一個類那里將屬性和方法直接繼承下來,從而減少重復代碼的編寫。提供繼承信息的我們稱之為父類,也叫超類或基類;得到繼承信息的我們稱之為子類,也叫派生類或衍生類。子類除了繼承父類提供的屬性和方法,還可以定義自己特有的屬性和方法,所以子類比父類擁有的更多的能力,在實際開發(fā)中,我們經常會用子類對象去替換掉一個父類對象,這是面向對象編程中一個常見的行為,對應的原則稱之為里氏替換原則。下面我們先看一個繼承的例子。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_tv(self):
if self._age >= 18:
print('%s正在觀看泡沫劇.' % self._name)
else:
print('%s只能觀看《熊出沒》.' % self._name)
class Student(Person):
def __init__(self, name, age, grade):
super(Student, self).__init__(name, age)
# 下面是Python3中的用法
# super().__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在學習%s.' % (self._grade, self._name, course))
class Teacher(Person):
def __init__(self, name, age, title): # title表示老師的職位
super(Teacher, self).__init__(name, age)
# 下面是Python3中的用法
# super().__init__(name, age)
self._title = title
@property
def title(self):
return self._title
def teach(self, course):
print('%s%s正在講%s' % (self._name, self._title, course))
def main():
stu = Student('zjy', 15, '初三')
stu.study('數(shù)學')
stu.watch_tv()
t = Teacher('zyy', 38, '教授')
t.teach('Python程序設計.')
t.watch_tv
if __name__ == '__main__':
main()
面向對象的三大支柱 之 多態(tài)
子類在繼承了父類的方法后,可以對父類已有的方法給出新的實現(xiàn)版本,這個動作稱之為方法重寫(override)。通過方法重寫我們可以讓父類的同一個行為在子類中擁有不同的實現(xiàn)版本,當我們調用這個經過子類重寫的方法時,不同的子類對象會表現(xiàn)出不同的行為,這一現(xiàn)象就被稱作多態(tài)(poly-morphism)。
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta): # metaclass=ABCMeta必須要在3.5版本以上的Python環(huán)境下才可以啊
def __init__(self, nickname):
self._nickname = nickname
@abstractmethod # 在父類中定義抽象方法,當有子類繼承時,就要寫此抽象方法
def make_voice(self)
pass
class Dog(Pet):
def make_voice(self):
print('%s: 汪汪汪' % self._nickname)
class Cat(Pet):
def make_voice(self):
print('%s 喵喵喵' % self._nickname)
def main():
pets = [Dog('旺財'), Cat('凱蒂'), Dog('大黃')] # 一個寵物列表
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
在上面的代碼中,我們將
Pet
類處理成了一個抽象類,所謂抽象類就是不能夠創(chuàng)建對象的類,這種類的存在就是專門為了讓其他類去繼承它。
Python從語法層面并沒有像Java或C#那樣提供對抽象類的支持,但是我們可以通過
abc
模塊的
ABCMeta
元類和
abstractmethod
包裝器來達到抽象類的效果
,如果一個類中存在抽象方法那么這個類就不能夠實例化(創(chuàng)建對象)。上面的代碼中,
Dog
和
Cat
兩個子類分別對
Pet
類中的
make_voice
抽象方法進行了重寫并給出了不同的實現(xiàn)版本,當我們在main函數(shù)中調用該方法時,這個方法就表現(xiàn)出了多態(tài)行為(同樣的方法做了不同的事情)。
- 練習1:奧特曼打小怪獸
簡要描述:
- 奧特曼和小怪獸是敵對的兩方,兩者要進行戰(zhàn)斗。其父類稱之為“戰(zhàn)斗者”,有屬性戰(zhàn)斗者名稱、戰(zhàn)斗者生命值、alive(該屬性是判斷生命值是否大于0得出的:alive為True表示生命值大于0,表示還存活在游戲中),此外,有一個待繼承的表示攻擊形式的抽象方法attack()。
- 奧特曼類:除了有名字、生命值屬性外,又添加了魔法值屬性。具有繼承來的表示普通攻擊形式的attack()方法以及魔法攻擊形式的magic_attack()方法以及終級必殺技攻擊形式的huge_attack()方法,由于魔法值會因為使用了普通攻擊而增加,所以還具有增加魔法值的resume()方法。
- 小怪獸類:具有從父類繼承來的名字、聲明值屬性,以及普通攻擊形勢的attack()方法。
-
三個全局函數(shù):
1)is_any_alive():判斷一群小怪獸里還有沒有是活著的
2)select_alive_one():選中一只活著的小怪獸(用來與奧特曼戰(zhàn)斗)
3)display_info():顯示奧特曼和小怪獸的相關戰(zhàn)斗信息
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter(object, metaclass=ABCMeta):
"""戰(zhàn)斗者(父類),將由奧特曼(子類)、小怪獸(子類)繼承"""
# 通過__slots__魔法限定對象可以綁定的成員變量
__slots__ = ('_name', '_hp')
def __init__(self, name, hp):
"""
初始化方法
:param name: 名字
:param hp: 生命值
"""
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
@property
def alive(self):
return self._hp > 0
@abstractmethod
def attack(self, other):
"""
攻擊
:param other: 被攻擊的對象
"""
pass
class Ultraman(Fighter):
"""奧特曼"""
__slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp):
"""
初始化方法
:param name: 名字
:param hp: 生命值
:param mp: 魔法值
"""
super().__init__(name, hp)
self._mp = mp
def attack(self, other):
other.hp -= randint(15, 25)
def huge_attack(self, other):
"""
終級必殺技(打掉對方至少50點或四分之三的血)
:param other: 被攻擊的某個對象
:return: 使用成功返回True否則返回False
"""
if self._mp >= 50:
self._mp -= 50
injury = other.hp * 3 // 4
injury = injury if injury >= 50 else 50
other.hp -= injury
return True
else: # 使用終級必殺技需要消耗50魔法值,此為魔法值不足的情況,無法使用終級必殺技,改為使用普通攻擊
self.attack(other)
return False
def magic_attack(self, others):
"""
魔法攻擊
:param others: 被攻擊的群體(使用魔法攻擊可以一次性攻擊多個小怪獸)
:return: 使用魔法成功返回True否則返回False
"""
if self._mp >= 20:
self._mp -= 20
for temp in others:
if temp.alive:
temp.hp -= randint(10, 15)
return True
else: # 魔法值不足20時無法使用魔法攻擊
return False
def resume(self):
"""
恢復隨機點數(shù)的魔法值
:return: 返回新增的點數(shù)
"""
incr_point = randint(1, 10)
self._mp += incr_point
return incr_point
def __str__(self):
return '~~~%s奧特曼~~~\n' % self._name + \
'生命值:%d\n' % self._hp + \
'魔法值:%d\n' % self._mp
class Monster(Fighter):
"""小怪獸"""
__slots__ = ('_name', '_hp')
def attack(self, other):
other.hp -= randint(10, 20)
def __str__(self):
return '~~~%s小怪獸~~~\n' % self._name + \
'生命值:%d\n' % self._hp
def is_any_alive(monsters):
"""判斷有沒有小怪獸是活著的"""
for monster in monsters:
if monster.alive > 0:
return True
return False
def select_alive_one(monsters):
"""選中一只活著的小怪獸"""
monsters_num = len(monsters)
while True:
index = randrange(monsters_num)
monster = monsters[index]
if monster.alive > 0:
return monster
def display_info(ultraman, monsters):
"""顯示奧特曼和小怪獸的相關戰(zhàn)斗信息"""
print(ultraman) # 自動調用__str__方法
for monster in monsters:
print(monster, end='') # 自動調用__str__方法
def main():
u = Ultraman('zjy', 1000, 120)
m1 = Monster('aaa', 250)
m2 = Monster('bbb', 500)
m3 = Monster('ccc', 750)
ms = [m1, m2, m3]
fight_round = 1 # 大戰(zhàn)回合數(shù)
while u.alive and is_any_alive(ms): # alive()是父級中的用來得到alive變量的get方法,該方法用來判斷_hp 是否大于0 只有大于0才說明戰(zhàn)斗者活著,只有活著才能進行下面的動作。活著的話就返回True,True == 1
print('=========第%02d回合========' % fight_round)
m = select_alive_one(ms) # 選中一只小怪獸參與戰(zhàn)斗
skill = randint(1, 10) # 通過隨機數(shù)選擇使用哪種技能
if skill <= 6: # skill為1 ~ 6時,奧特曼使用普通攻擊
print('%s使用普通攻擊打了%s.' % (u.name, m.name))
u.attack(m)
print('%s的魔法值恢復了%d點.' % (u.name, u.resume())) # 每使用一次普通攻擊,魔法值都會有一定補給
elif skill <= 9: # skill為6 ~ 9時,奧特曼使用魔法攻擊
if u.magic_attack(ms):
print('%s成功使用魔法攻擊了小怪獸群體.' % u.name)
else:
print('%s魔法值不足,魔法攻擊失敗.' % u.name)
else: # skill為10時,奧特曼使用終級必殺技
if u.huge_attack(m):
print('%s使用終級必殺技虐了%s.' % (u.name, m.name))
else:
print('%s使用普通攻擊打了%s.' % (u.name, m.name))
print('%s的魔法值恢復了%d點.' % (u.name, u.resume())) # 每使用一次普通攻擊,魔法值都會有一定補給
if m.alive > 0: # 如果與奧特曼戰(zhàn)斗的小怪獸沒有死,就回擊奧特曼
# 不能替換成if m.alive() == True!因為alive其實是個get到的成員變量...
print('%s回擊了%s.' % (m.name, u.name))
m.attack(u)
display_info(u, ms) # 每個回合結束后顯示奧特曼和小怪獸的信息
fight_round += 1
print('\n========戰(zhàn)斗結束!========\n')
if u.alive > 0:
print('%s奧特曼勝利!' % u.name)
else:
print('小怪獸勝利!')
if __name__ == '__main__':
main()
- 練習2:撲克游戲
簡要說明
-
“一張牌”類
屬性:花色、點數(shù)
方法:用來以統(tǒng)一的格式輸出每張牌的__str__方法 -
“一副牌”類
屬性:保存了一整副牌的cards屬性、用來判斷是否發(fā)了牌的current屬性
方法:洗牌、發(fā)牌、判斷牌是否已全部發(fā)完 -
“玩家”類
屬性:玩家姓名name、 玩家手中的所有牌cards_on_hand
方法:摸牌、整理手中的牌 - 全局函數(shù)get_key(card)用來定義排序規(guī)則-先根據花色再根據點數(shù)排序。
import random
class Card(object):
"""一張牌"""
def __init__(self, suite, face):
"""
:suite: 花色
:face: 點數(shù)
"""
self._suite = suite
self._face = face
@property
def face(self):
return self._face
@property
def suite(self):
return self._suite
def __str__(self):
if self._face == 1:
face_str = 'A'
elif self._face == 11:
face_str = 'J'
elif self._face == 12:
face_str = 'Q'
elif self._face == 13:
face_str = 'K'
else:
face_str = str(self._face)
return '%s%s' % (self._suite, face_str)
def __repr__(self): # 類的一個專有方法,用來打印、轉換
return self.__str__()
class Poker(object):
"""一副牌"""
def __init__(self):
"""
:cards: 保存了一整副牌
:current: 用來判斷是否發(fā)了牌:current為0表示還沒有發(fā)牌;每發(fā)一張拍current就會加1;同時也作為cards的索引,從而可以取到每一張牌
"""
self._cards = [Card(suite, face) # ???這個初始化看不懂????????????????????????????
for suite in '????'
for face in range(1, 14)] # 沒有大王小王...
self._current = 0 # _cards _current都是成員變量嗎?參數(shù)里面沒有啊?所以意思是只要出現(xiàn)在__init__函數(shù)中的就代表是成員變量??
@property
def cards(self):
return self._cards
def shuffle(self):
"""洗牌(隨機亂序)"""
self._current = 0
random.shuffle(self._cards) # 這不是有吊用自己了嗎?這怎么行。。。?????????????????????????????
@property
def next(self):
"""
發(fā)牌
:return: 返回發(fā)的那張牌
"""
card = self._cards[self._current] # current作為索引,取到將要發(fā)的牌self._cards[self._current]
self._current += 1 # 每發(fā)一張牌,surrent就變化
return card
@property
def has_next(self):
"""還有沒有牌"""
return self._current < len(self._cards) # 判斷是否發(fā)了牌,如果發(fā)完了牌,那么current的值應該是52(沒有大王小王)
class Player(object):
"""玩家"""
def __init__(self, name):
"""
:name: 玩家姓名
:cards_on_hand: 玩家手中的牌
"""
self._name = name
self._cards_on_hand = []
@property
def name(self):
return self._name
@property
def cards_on_hand(self):
return self._cards_on_hand
def get(self, card):
"""摸牌"""
self._cards_on_hand.append(card)
def arrange(self, card_key):
"""玩家整理手上的牌"""
self._cards_on_hand.sort(key=card_key) # ?????????????????????????????????????????????
# 排序規(guī)則-先根據花色再根據點數(shù)排序
def get_key(card):
return (card.suite, card.face)
def main():
# 創(chuàng)建一副牌
p = Poker()
# 洗牌
p.shuffle()
# 建立玩家
players = [Player('東邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(13): # 牌的點數(shù)在1 ~ 13之間,所以這里進行限制
for player in players: # 玩家依次摸牌
player.get(p.next) # 玩家使用get()方法摸牌,摸到的牌是由next()發(fā)的
for player in players:
print(player.name + ':', end=' ')
player.arrange(get_key) # 玩家整理手中的牌?????????為什么參數(shù)是get_key,get_key是函數(shù)啊,不應該get_key()?
print(player.cards_on_hand) # cards_on_hand是個列表,保存了某個玩家的所有手中的牌
if __name__ == '__main__':
main()
- 練習3:工資結算系統(tǒng)
"""
某公司有三種類型的員工 分別是部門經理、程序員和銷售員
需要設計一個工資結算系統(tǒng) 根據提供的員工信息來計算月薪
部門經理的月薪是每月固定15000元
程序員的月薪按本月工作時間計算 每小時150元
銷售員的月薪是1200元的底薪加上銷售額5%的提成
"""
from abc import ABCMeta, abstractmethod
class Employee(object, mataclass=ABCMeta):
"""員工"""
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
"""
獲得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部門經理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序員"""
def __init__(self, name, working_hour=0):
super.__init__(name)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""銷售員"""
def __init__(self, name, sale_num=0):
super.__init__(name)
self._sale_num = sale_num
@property
def sale_num(self):
return self._sale_num
@sale_num.setter
def sale_num(self, sale_num):
self._sale_num = sale_num if sale_num > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('劉備'), Programmer('諸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('呂布'), Programmer('張遼'),
Programmer('趙云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('請輸入%s本月工作時間: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('請輸入%s本月銷售額: ' % emp.name))
# 同樣是接收get_salary這個消息但是不同的員工表現(xiàn)出了不同的行為(多態(tài))
print('%s本月工資為: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()
三、圖形用戶界面和游戲開發(fā)
1、基于tkinter模塊的GUI
Python默認的GUI開發(fā)模塊是tkinter(在Python 3以前的版本中名為Tkinter),從這個名字就可以看出它是基于Tk的,Tk是一個工具包,最初是為Tcl設計的,后來被移植到很多其他的腳本語言中,它提供了跨平臺的GUI控件。當然Tk并不是最新和最好的選擇,也沒有功能特別強大的GUI控件,事實上,開發(fā)GUI應用并不是Python最擅長的工作,如果真的需要使用Python開發(fā)GUI應用,wxPython、PyQt、PyGTK等模塊都是不錯的選擇。
基本上使用tkinter來開發(fā)GUI應用需要以下5個步驟:
- 導入tkinter模塊中我們需要的東西。
- 創(chuàng)建一個頂層窗口對象并用它來承載整個GUI應用。
- 在頂層窗口對象上添加GUI組件。
- 通過代碼將這些GUI組件的功能組織起來。
- 進入主事件循環(huán)(main loop)。
下面的代碼演示了如何使用tkinter做一個簡單的GUI應用。
# Step1、導入tkinter模塊中我們需要的東西
import tkinter
import tkinter.messagebox
def main():
flag = True
# 定義按鈕觸發(fā)的事件:修改標簽上的文字?????????????
def change_label_text():
nonlocal flag
flag = not flag
color, msg = ('red', 'Hello, world!')\
if flag else ('blue', 'Goodbye, world!')
label.config(text=msg, fg=color)
# 定義按鈕觸發(fā)的事件:確認退出?????????????
def confirm_to_quit():
if tkinter.messagebox.askokcancel('溫馨提示', '確定要退出嗎?'):
top.quit() # 退出頂級會話窗口
# Step2、創(chuàng)建一個頂層窗口對象并用它來承載整個GUI應用
top = tkinter.Tk()
# 2.1 設置頂層窗口的大小
top.geometry('240x160') # 小寫字母x
# 2.2 設置窗口標題
top.title('小游戲')
# Step3、在頂層窗口對象上添加GUI組件
# 3.1 創(chuàng)建標簽對象并添加到頂層窗口
label = tkinter.Label(top, text='Hello, world!', font='Arial -32', fg='red')
label.pack(expand=1)
# 3.2 創(chuàng)建按鈕對象
# 3.2.1 創(chuàng)建一個裝按鈕的容器
panel = tkinter.Frame(top)
# 3.2.2 創(chuàng)建按鈕對象:指定添加到哪個容器中,通過command參數(shù)綁定事件回調函數(shù)
button1 = tkinter.Button(panel, text='修改', command=change_label_text)
button1.pack(side='left') # pack方法用來設置組件放置的位置
button2 = tkinter.Button(panel, text='退出', command=confirm_to_quit)
button2.pack(side='right')
panel.pack(side='bottom')
# Step4、開啟主事件循環(huán)
tkinter.mainloop()
if __name__ == '__main__':
main()
點擊“修改”:
點擊“退出”:
需要說明的是,GUI應用通常是事件驅動式的,之所以要進入主事件循環(huán)就是要監(jiān)聽鼠標、鍵盤等各種事件的發(fā)生并執(zhí)行對應的代碼對事件進行處理,因為事件會持續(xù)的發(fā)生,所以需要這樣的一個循環(huán)一直運行著等待下一個事件的發(fā)生。另一方面,Tk為控件的擺放提供了三種布局管理器,通過布局管理器可以對控件進行定位,這三種布局管理器分別是:Placer(開發(fā)者提供控件的大小和擺放位置)、Packer(自動將控件填充到合適的位置)和Grid(基于網格坐標來擺放控件),此處不進行贅述。
2、使用Pygame進行游戲開發(fā)
Pygame 是一個開源的Python模塊,專門用于多媒體應用(如電子游戲)的開發(fā),其中 包含對圖像、聲音、視頻、事件、碰撞等的支持 。Pygame建立在SDL的基礎上,SDL是一套跨平臺的多媒體開發(fā)庫,用C語言實現(xiàn),被廣泛的應用于游戲、模擬器、播放器等的開發(fā)。而 Pygame讓游戲開發(fā)者不再被底層語言束縛,可以更多的關注游戲的功能和邏輯 。
下面我們來完成一個簡單的小游戲,游戲的名字叫“大球吃小球”,當然完成這個游戲并不是重點,學會使用Pygame也不是重點,最重要的我們要在這個過程中 體會如何使用前面講解的面向對象程序設計 , 學會用這種編程思想去解決現(xiàn)實中的問題 。
注意:開始之前,需要下載Pygame模塊,下載步驟請點擊這里。
制作游戲窗口
import pygame
def main():
# 初始化導入的pygame中的模塊
pygame.init()
# 初始化用于顯示的窗口并設置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 設置當前窗口的標題
pygame.display.set_caption('大球吃小球')
running = True
# 開啟一個事件循環(huán)處理發(fā)生的事件
while running:
# 從消息隊列中獲取事件并對事件進行處理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
在窗口中繪圖
可以通過pygame中draw模塊的函數(shù)在窗口上繪圖,可以繪制的圖形包括:線條、矩形、多邊形、圓、橢圓、圓弧等。需要說明的是,屏幕坐標系是將屏幕左上角設置為坐標原點(0, 0),向右是x軸的正向,向下是y軸的正向,在表示位置或者設置尺寸的時候,我們默認的單位都是像素。所謂像素就是屏幕上的一個點,你可以用瀏覽圖片的軟件試著將一張圖片放大若干倍,就可以看到這些點。pygame中表示顏色用的是色光三原色表示法,即通過一個元組或列表來指定顏色的RGB值,每個值都在0~255之間,因為是每種原色都用一個8位(bit)的值來表示,三種顏色相當于一共由24位構成,這也就是常說的“24位顏色表示法”。
import pygame
def main():
# 初始化導入的pygame中的模塊
pygame.init()
# 初始化用于顯示的窗口并設置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 設置當前窗口的標題
pygame.display.set_caption('大球吃小球')
### 新增部分START
# 設置窗口的背景色(顏色是由紅綠藍三原色構成的元組)
screen.fill((242, 242, 242))
# 繪制一個圓(參數(shù)分別是: 屏幕, 顏色, 圓心位置, 半徑, 0表示填充圓)
pygame.draw.circle(screen, (255, 0, 0), (100, 100), 30, 0)
# 刷新當前窗口(渲染窗口將繪制的圖像呈現(xiàn)出來)
pygame.display.flip()
### 新增部分END
running = True
# 開啟一個事件循環(huán)處理發(fā)生的事件
while running:
# 從消息隊列中獲取事件并對事件進行處理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
加載圖像
如果需要直接加載圖像到窗口上,可以使用pygame中image模塊的函數(shù)來加載圖像,再通過之前獲得的窗口對象的blit方法渲染圖像,代碼如下所示。
import pygame
def main():
# 初始化導入的pygame中的模塊
pygame.init()
# 初始化用于顯示的窗口并設置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 設置當前窗口的標題
pygame.display.set_caption('大球吃小球')
### 新增部分START
# 設置窗口的背景色(顏色是由紅綠藍三原色構成的元組)
screen.fill((255, 255, 255))
# 通過指定的文件名加載圖像
ball_image = pygame.image.load('E:/fdj.png')
# 在窗口上渲染圖像
screen.blit(ball_image, (50, 50))
### 新增部分END
# - 繪制一個圓(參數(shù)分別是: 屏幕, 顏色, 圓心位置, 半徑, 0表示填充圓)
# - pygame.draw.circle(screen, (255, 0, 0), (100, 100), 30, 0)
# 刷新當前窗口(渲染窗口將繪制的圖像呈現(xiàn)出來)
pygame.display.flip()
running = True
# 開啟一個事件循環(huán)處理發(fā)生的事件
while running:
# 從消息隊列中獲取事件并對事件進行處理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if __name__ == '__main__':
main()
實現(xiàn)動畫效果
說到動畫這個詞大家都不會陌生,事實上要實現(xiàn)動畫效果,本身的原理也非常簡單,就是 將不連續(xù)的圖片連續(xù)的播放 ,只要 每秒鐘達到了一定的幀數(shù) ,那么就可以做出比較流暢的動畫效果。 如果要讓上面代碼中的小球動起來,可以將小球的位置用變量來表示,并在循環(huán)中修改小球的位置再刷新整個窗口即可 。
import pygame
def main():
# 初始化導入的pygame中的模塊
pygame.init()
# 初始化用于顯示的窗口并設置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 設置當前窗口的標題
pygame.display.set_caption('大球吃小球')
### 增加/修改部分START
# 定義變量來表示小球在屏幕上的位置
x, y = 50, 50
running = True
# 開啟一個事件循環(huán)處理發(fā)生的事件
while running:
# 從消息隊列中獲取事件并對事件進行處理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 設置窗口的背景色(顏色是由紅綠藍三原色構成的元組)
screen.fill((255, 255, 255))
# 繪制一個圓(參數(shù)分別是: 屏幕, 顏色, 圓心位置, 半徑, 0表示填充圓)
pygame.draw.circle(screen, (255, 0, 0,), (x, y), 30, 0)
# 刷新當前窗口(渲染窗口將繪制的圖像呈現(xiàn)出來)
pygame.display.flip()
# 每隔50毫秒就改變小球的位置再刷新窗口
pygame.time.delay(50)
x, y = x + 5, y + 5
### 增加/修改部分END
if __name__ == '__main__':
main()
碰撞檢測
通常一個游戲中會有很多對象出現(xiàn),而這些對象之間的“碰撞”在所難免,比如炮彈擊中了飛機、箱子撞到了地面等。 碰撞檢測在絕大多數(shù)的游戲中都是一個必須得處理的至關重要的問題 , pygame的sprite(動畫精靈)模塊就提供了對碰撞檢測的支持 ,這里我們暫時不介紹sprite模塊提供的功能,因為 要檢測兩個小球有沒有碰撞其實非常簡單,只需要檢查球心的距離有沒有小于兩個球的半徑之和 。 為了制造出更多的小球,我們可以通過對鼠標事件的處理,在點擊鼠標的位置創(chuàng)建顏色、大小和移動速度都隨機的小球,當然要做到這一點,我們可以把之前學習到的面向對象的知識應用起來 。
from enum import Enum, unique
from math import sqrt
from random import randint
import pygame
@unique
class Color(Enum):
"""顏色"""
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (242, 242, 242)
@staticmethod
def random_color():
"""獲得隨機顏色"""
r = randint(0, 255)
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
class Ball(object):
"""球"""
def __init__(self, x, y, radius, sx, sy, color=Color.RED):
"""初始化方法"""
self.x = x
self.y = y
self.radius = radius
self.sx = sx
self.sy = sy
self.color = color
self.alive = True
def move(self, screen):
"""移動"""
self.x += self.sx
self.y += self.sy
if self.x - self.radius <= 0 or self.x + self.radius >= screen.get_width():
# 超過邊界了,所以去“對過”
self.sx = -self.sx
if self.y - self.radius <= 0 or self.y + self.radius >= screen.get_height():
self.sy = -self.sy
def eat(self, other):
"""吃其他球"""
if self.alive and other.alive and self != other:
dx, dy = self.x - other.x, self.y - other.y
distance = sqrt(dx ** 2 + dy ** 2)
if distance < self.radius + other.radius and self.radius > other.radius:
# 檢查球心的距離有沒有小于兩個球的半徑之和,如果小于,則沒有碰撞;
# 檢查本球的半徑是否大于其他球的半徑,大于的話,才能吃其他球。
other.alive = False
self.radius = self.radius + int(other.radius * 0.146)
def draw(self, screen):
"""在窗口上繪制球"""
pygame.draw.cicle(screen, self.color, (self.x, self.y), self.radius, 0)
事件處理
可以在事件循環(huán)中對 鼠標事件 進行處理, 通過事件對象的type屬性可以判定事件類型,再通過pos屬性就可以獲得鼠標點擊的位置 。如果要處理 鍵盤事件 也是在這個地方,做法與處理鼠標事件類似。
from enum import Enum, unique
from math import sqrt
from random import randint
import pygame
@unique
class Color(Enum):
"""顏色"""
# 設置下面這些有何用?
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (242, 242, 242)
@staticmethod
def random_color():
"""獲得隨機顏色"""
r = randint(0, 255)
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
class Ball(object):
"""球"""
def __init__(self, x, y, radius, sx, sy, color=Color.RED):
"""
初始化方法
:x/y: 球的圓心所在的位置的坐標
:sx/sy: 將要在x/y軸上移動的距離
:radius: 圓的半徑
:color: 圓的填充色
"""
self.x = x
self.y = y
self.radius = radius
self.sx = sx
self.sy = sy
self.color = color
self.alive = True
def move(self, screen):
"""移動"""
self.x += self.sx
self.y += self.sy
if self.x - self.radius <= 0 or self.x + self.radius >= screen.get_width():
# 超過邊界了,所以去“對過”
self.sx = -self.sx
if self.y - self.radius <= 0 or self.y + self.radius >= screen.get_height():
self.sy = -self.sy
def eat(self, other):
"""吃其他球"""
if self.alive and other.alive and self != other:
dx, dy = self.x - other.x, self.y - other.y
distance = sqrt(dx ** 2 + dy ** 2)
if distance < self.radius + other.radius and self.radius > other.radius:
# 檢查球心的距離有沒有小于兩個球的半徑之和,如果小于,則沒有碰撞;
# 檢查本球的半徑是否大于其他球的半徑,大于的話,才能吃其他球。
other.alive = False
self.radius = self.radius + int(other.radius * 0.146)
def draw(self, screen):
"""在窗口上繪制球"""
pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius, 0)
def main():
# 1、定義用來裝有所有球的容器
balls = []
# 2、初始化導入的pygame中的模塊
pygame.init()
# 3.1、初始化 用于顯示的窗口 并 設置窗口尺寸
screen = pygame.display.set_mode((800, 600))
# 3.2、設置當前窗口的標題
pygame.display.set_caption('大球吃小球')
running = True
# 4、開啟一個事件循環(huán)處理發(fā)生的事件
while running:
# 4.1、從消息隊列中獲取事件并對事件進行處理——鼠標點擊到的位置將創(chuàng)建一個球
for event in pygame.event.get():# ?????如何結束這個for循環(huán)呢,這個不斷創(chuàng)建小球的for循環(huán)??
# 4.1.0、判斷用戶是否點擊了關閉按鈕
if event.type == pygame.QUIT:
running = False
# 4.1.1、通過事件對象的type屬性可以判定事件類型,下面是處理鼠標事件的代碼
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
# 4.1.1.1、通過pos屬性獲得點擊鼠標的位置
x, y = event.pos
# 4.1.1.2、在點擊鼠標的位置創(chuàng)建一個球(大小、速度和顏色隨機)
radius = randint(10, 100) # 半徑
sx, sy = randint(-10, 10), randint(-10, 10)
color = Color.random_color()
ball = Ball(x, y, radius, sx, sy, color)
# 4.1.1.3、將球添加到列表容器中
balls.append(ball)
# 4.2、設置窗口的背景色(顏色是由紅綠藍三原色構成的元組)
screen.fill((255, 255, 255))
# 4.3、取出容器中的球,如果沒被吃掉就繪制,被吃掉了就移除
for ball in balls:
if ball.alive:
ball.draw(screen)
else:
balls.remove(ball) # ???????????//沒有此方法啊
# 4.4、刷新當前窗口(渲染窗口將繪制的圖像呈現(xiàn)出來)
pygame.display.flip()
# 4.5、每隔50毫秒就改變球的位置再刷新窗口
pygame.time.delay(50)
for ball in balls:
ball.move(screen)
# 檢查球有沒有吃到其他的球
for other in balls:
ball.eat(other)
if __name__ == '__main__':
main()
(PS. 運行過程并不能深入理解,因為還沒有學習event事件等相關內容,此處暫時不處理,先繼續(xù)向下學習。否則自己去找資源學習的話,不一定能學到準確的點上,且目前也不是針對那些的學習。)
運行效果如下:用戶可以一直點擊屏幕,每點擊一個地方就出現(xiàn)一個圓,這些圓不停地移動,當發(fā)生碰撞時,大球將消滅掉小球,同時大球的半徑會增加。
準確的說它算不上一個游戲,但是做一個小游戲的基本知識我們已經通過這個例子告訴大家了,有了這些知識已經可以開始你的小游戲開發(fā)之旅了。其實上面的代碼中還有很多值得改進的地方,比如刷新窗口以及讓球移動起來的代碼并不應該放在事件循環(huán)中,等學習了多線程的知識后,用一個后臺線程來處理這些事可能是更好的選擇。如果希望獲得更好的用戶體驗,我們還可以在游戲中加入背景音樂以及在球與球發(fā)生碰撞時播放音效,利用pygame的mixer和music模塊,我們可以很容易的做到這一點,大家可以自行了解這方面的知識。事實上,想了解更多的關于pygame的知識,最好的教程是pygame的官方網站,如果英語沒毛病就可以趕緊去看看啦。 如果想開發(fā)3D游戲,pygame就顯得力不從心了,對3D游戲開發(fā)如果有興趣的讀者不妨看看Panda3D。
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
