學習Python第三周總結
一、函式和模塊
1.1 函式的定義
? Python中的函式的自變數稱為函式的引數,而因變數稱為函式的回傳值
在Python中可以使用def關鍵字來定義函式,命名規則跟變數的命名規則是一致的,在函式名后面的圓括號中可以放置傳遞給函式的引數,就是我們剛才說到的函式的自變數,而函式執行完成后我們會通過return關鍵字來回傳函式的執行結果,就是我們剛才說的函式的因變數,一個函式要執行的代碼塊(要做的事情)也是通過縮進的方式來表示的,跟之前分支和回圈結構的代碼塊是一樣的,
1.2函式的引數
如果函式中沒有return陳述句,那么函式默認回傳代表空值的None,另外,在定義函式時,函式也可以沒有自變數,但是函式名后面的圓括號是必須有的,
-
位置引數——在沒有特殊處理的情況下,函式的引數都是位置引數
-
關鍵字引數——我們在設計函式時,如果既不知道呼叫者會傳入的引數個數,也不知道呼叫者會不會指定引數名,那么同時使用可變引數和關鍵字引數,關鍵字引數會將傳入的帶引數名的引陣列裝成一個字典,引數名就是字典中鍵值對的鍵,而引數值就是字典中鍵值對的值
重點提醒:關鍵字引數一定要在位置引數的后面,
代碼例子如下:
# *args--->可變引數--->可以接收零個或任意多個位置引數--->將位置引數打包成元組
# **kwargs--->可以接收零個或任意多個關鍵字引數--->將所有的關鍵字引數打包成一個字典
def add(*args, **kwargs):
print(args, type(args))
# print(kwargs, type(kwargs))
total = 0
for arg in args:
if type(arg) in (int, float):
total += arg
for value in kwargs.values():
if type(value) in (int, float):
total += value
return total
print(add(1, 2, 4, a=3))
def mul(*args, **kwargs):
# print(args, type(args))
# print(kwargs, type(kwargs))
total = 1
for arg in args:
if type(arg) in (int, float):
total *= arg
for value in kwargs.values():
if type(value) in (int, float):
total *= value
return total
print(mul(1, 2, 4, a=3))
函式的例題一:
玩家搖兩顆骰子,如果第一次搖出了7點或11點,玩家勝;如果搖出了2點、3點、12點,莊家勝;
如果搖出了其他的點數,游戲繼續,玩家重新搖色子;如果玩家搖出了第一次搖的點數,玩家勝;
如果玩家搖出了7點,莊家勝;如果玩家搖出其他點數,游戲繼續,玩家重新搖色子,直到分出勝負,
游戲開始之前,玩家有1000元的初始資金,玩家可以下注,贏了獲得下注的金額,輸了就扣除下注的金額,
游戲結束的條件是玩家把錢輸光,
import random
def roll_dice(num):
"""
搖骰子
:param num: 骰子的數量
:return: 搖出的點數
"""
total = 0
for _ in range(num):
total += random.randrange(1, 7)
return total
def win():
global x
print('玩家勝')
x += z
def lose():
global x
print('莊家勝')
x -= z
x = 1000
while x > 0:
print(f'玩家總資產為{x}元')
z = 0
while z <= 0 or z > x:
z = int(input('請下注'))
m = roll_dice(3)
print(f'玩家搖出了{m}點')
if m in (7, 11):
win()
elif m in (2, 3, 12):
lose()
else:
while True:
n = roll_dice(3)
print(f'玩家搖出了{n}點')
if n == m:
win()
break
elif n == 7:
lose()
break
print('玩家已破產,游戲結束')
例題二:
# 求階乘
def fac(num):
"""求階乘"""
result = 1
for i in range(2, num + 1):
result *= i
return result
m = int(input('m = '))
n = int(input('n = '))
print(fac(m) // fac(n) // fac(m - n))
1.3標準庫中的模塊和函式
Python標準庫中提供了大量的模塊和函式來簡化我們的開發作業,random模塊可以提供生成亂數和進行隨機抽樣的函式;而time模塊則可以提供和時間操作相關的函式,在Python標準庫中的math模塊中還包括了計算正弦、余弦、指數、對數等一系列的數學函式,隨著我們進一步的學習Python編程知識,我們還會用到更多的模塊和函式,
Python標準庫中還有一類函式是不需要import就能夠直接使用的,我們將其稱之為內置函式,這些內置函式都是很有用也是最常用的,下面的表格列出了一部分的內置函式,
| 函式 | 說明 |
|---|---|
abs | 回傳一個數的絕對值,例如:abs(-1.3)會回傳1.3, |
bin | 把一個整數轉換成以'0b'開頭的二進制字串,例如:bin(123)會回傳'0b1111011', |
chr | 將Unicode編碼轉換成對應的字符,例如:chr(8364)會回傳'€', |
hex | 將一個整數轉換成以'0x'開頭的十六進制字串,例如:hex(123)會回傳'0x7b', |
input | 從輸入中讀取一行,回傳讀到的字串, |
len | 獲取字串、串列等的長度, |
max | 回傳多個引數或一個可迭代物件(后面會講)中的最大值,例如:max(12, 95, 37)會回傳95, |
min | 回傳多個引數或一個可迭代物件(后面會講)中的最小值,例如:min(12, 95, 37)會回傳12, |
oct | 把一個整數轉換成以'0o'開頭的八進制字串,例如:oct(123)會回傳'0o173', |
open | 打開一個檔案并回傳檔案物件(后面會講), |
ord | 將字符轉換成對應的Unicode編碼,例如:ord('€')會回傳8364, |
pow | 求冪運算,例如:pow(2, 3)會回傳8;pow(2, 0.5)會回傳1.4142135623730951, |
print | 列印輸出, |
range | 構造一個范圍序列,例如:range(100)會產生0到99的整數序列, |
round | 按照指定的精度對數值進行四舍五入,例如:round(1.23456, 4)會回傳1.2346, |
sum | 對一個序列中的項從左到右進行求和運算,例如:sum(range(1, 101))會回傳5050, |
type | 回傳物件的型別,例如:type(10)會回傳int;而type('hello')會回傳str, |
二、高階函式
? Python中的函式是一等函式,但是函式本身也可以作為函式的引數或回傳值,而且還可以賦值給變數,這就是所謂的高階函式,通常使用高階函式可以實作對原有函式的解藕合操作,
Lambda函式——沒有名字而且一句話就能寫完的函式,唯一的運算式就是函式的回傳值,也被稱為匿名函式
例如:
# fn ---> 一個實作二元運算的函式(可以做任意的二元運算)
def calc(*args, op, init_value=0, **kwargs):
total = init_value
for arg in args:
if type(arg) in (int, float):
total = op(total, arg)
for value in kwargs.values():
if type(value) in (int, float):
total = op(total, value)
return total
# def add(x, y):
# return x + y
#
#
# def mul(x, y):
# return x * y
# print(calc(11, 22, 33, 44, op=add))
print(calc(11, 22, 33, 44, op=lambda x, y: x + y))
# print(calc(11, 22, 33, 44, op=mul, init_value=1))
print(calc(11, 22, 33, 44, init_value=1, op=lambda x, y: x * y))
fn = lambda x, y: x - y
print(calc(11, 22, 33, 44, init_value=100, op=fn))
三、遞回呼叫
? 函式如果直接或者間接的呼叫了自身,那么這種呼叫就被稱為遞回呼叫,
遞回函式的兩個要點:
- 遞回公式(第n次跟第n-1次的關系)
- 收斂條件(什么時候停止遞回呼叫)
# 不管函式是呼叫別的函式,還是呼叫自身,一定要做到快速收斂,
# 在比較有限的呼叫次數內能夠結束,而不是無限制的呼叫函式,
# 如果一個函式(通常指遞回呼叫的函式)不能夠快速收斂,那么就很有可能產生下面的錯誤
# RecursionError: maximum recursion depth exceeded
# 最終導致程式的崩潰,
def fac(num: int) -> int:
"""求階乘(遞回寫法)"""
if num == 0:
return 1
return num * fac(num - 1)
if __name__ == '__main__':
# return 5 * fac(4)
# return 4 * fac(3)
# return 3 * fac(2)
# return 2 * fac(1)
# return 1 * fac(0)
# return 1
print(fac(5))
def fib(n):
if n in (1, 2):
return 1
return fib(n - 1) + fib(n - 2)
if __name__ == '__main__':
for i in range(1, 21):
print(i, fib(i))
例題一:撰寫實作對串列元素進行冒泡排序的函式
def bubble_sort(items, ascending=True, gt=lambda x, y: x > y):
"""
冒泡排序
:param items: 待排序的串列
:param ascending:
:param gt:
:return:
"""
items = items[:]
for i in range(1, len(items)):
swapped = False
for j in range(0, len(items) - i):
if gt(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j]
swapped = True
if not swapped:
break
if not ascending:
items = items[::-1]
return items
if __name__ == '__main__':
nums = [35, 96, 12, 7, 20, 8, 15]
print(bubble_sort(nums, ascending=False))
print(nums)
例題二:撰寫實作查找串列元素的函式:
def seq_search(items: list, key) -> int:
"""
順序查找
:param items: 待查找的元素
:param key: 要找的元素
:return: 找到了回傳元素的索引,找不到回傳-1
"""
for index, item in enumerate(items):
if item == key:
return index
return -1
def bin_search(items: list, key) -> int:
"""
二分查找
:param items:待查找的元素(元素有序)
:param key: 要找的元素
:return: 找到了回傳元素的索引,找不到回傳-1
"""
start, end = 0, len(items) - 1
while start <= end:
mid = (start + end) // 2
if key > items[mid]:
start = mid + 1
elif key < items[mid]:
end = mid - 1
else:
return mid
return -1
if __name__ == '__main__':
nums1 = [5, 4, 7, 20, 8, 15]
print(seq_search(nums1, 20))
print('_' * 20)
nums2 = [4, 5, 7, 8, 15, 20]
print(bin_search(nums2, 15))
print(bin_search(nums2, 45))
四、面向物件編程
1、定義
面向物件編程是一種編程范式(程式設計的方法論),
如果要用一句話來概括面向物件編程,我認為下面的說法是相當精準的,
面向物件編程:把一組資料和處理資料的方法組成物件,把行為相同的物件歸納為類,通過封裝隱藏物件的內部細節,通過繼承實作類的特化和泛化,通過多型實作基于物件型別的動態分派,
物件:物件是可以接收訊息的物體,面向物件編程就行通過給物件發訊息達到解決問題的目標,物件 = 資料 + 函式(方法),即物件將資料和操作資料的函式從邏輯上變成了一個整體,
物件有以下四個特征:
- 一切皆為物件
- 物件都有屬性和行為
- 每個物件都是獨一無二的
- 物件一定屬于某個類
類:將有共同特征(靜態特征和動態特征)的物件的共同特征抽取出來之后得到的一個抽象概念,簡單的說,類是物件的藍圖(模板),有了類才能夠創建出這種型別的物件,
2、創建和使用物件
1、定義類:類的命名使用駝峰命名法(每個單詞首字母大寫)
? 資料抽象:找到和物件相關的靜態特征(屬性)
? 行為抽象:找到和物件相關的動態特征(方法)
2、創建物件
3、給物件發訊息
例如:
# 第一步:定義類
class Student:
"""學生"""
# 資料抽象(屬性)
def __init__(self, name, age):
self.name = name
self.age = age
# 行為抽象(方法)
def eat(self, name):
"""吃飯"""
print(f'{self.name}正在吃{name}')
def study(self, course_name):
"""
學習
:param course_name: 課程名字
"""
print(f'{self.name}正在學習{course_name}')
def play(self, game_name):
"""
玩耍
:param game_name: 游戲名字
"""
print(f'{self.name}正在玩{game_name}')
# 第二步:創建物件--->構造器語法--->類名(..., ...)
Stu1 = Student('黃小宇', 18)
Stu2 = Student('周大大', 21)
# Student.study(stu1, 'Python程式設計')
# 第三步:給物件發訊息(呼叫物件的方法)
Stu1.eat('螺螄粉')
Stu2.study('Python程式設計')
3、魔術方法
-
魔術方法(魔法方法)—> 有特殊用途和意義的方法
- init —> 初始化方法,在呼叫構造器語法創建物件的時候會被自動呼叫
- str —> 獲得物件的字串表示,在呼叫print函式輸出物件時會被自動呼叫
-
repr —> 獲得物件的字串表示,把物件放到容器中呼叫print輸出時會自動呼叫
—> representation - lt —> 在使用 < 運算子比較兩個物件大小時會自動呼叫
如果一個變數的取值只有有限個選項,可以考慮使用列舉型別,
Python中沒有定義列舉型別的語法,但是可以通過繼承Enum類來實作列舉型別,
結論1:列舉型別是定義符號常量的最佳選擇!!!
結論2:符號常量(有意義的名字)總是優于字面常量!!
4、繼承和多型
繼承:對已有的類進行擴展創建出新的類,這個程序就叫繼承,
提供繼承資訊的類叫做父類(超類、基類),得到繼承資訊的類稱為子類(派生類),
繼承是實作代碼復用的一種手段,但是千萬不要濫用繼承,
繼承是一種is-a關系,
a student is a person.
a teacher is a person.
a programmer is a person.
子類直接從父類繼承公共的屬性和行為,再添加自己特有的屬性和行為,
所以子類一定是比父類更強大的,任何時候都可以用子類物件去替代父類物件,
Python中的繼承允許多重繼承,一個類可以有一個或多個父類,
如果不是必須使用多重繼承的場景下,請盡量使用單一繼承,
5、兩個類之間有哪些關系
~ is-a關系:繼承—>從一個類派生出另一個類
a student is a person
~ has-a關系:關聯—>把一個類的物件作為另外一個類的物件的屬性
a person has an identity card
——(普通)關聯
-
? —— 強關聯:整體和部分的關聯,聚合和合成
-
use-a關系:依賴—>一個類的物件作為另外一個類的方法的引數或回傳值
? a person use a vehicle(交通工具)
6、面向物件編程的四大支柱
1、抽象(abstraction):提取共性(定義類就是一個抽象程序,需要做資料抽象和行為抽象)
2、封裝(encapsulation):把資料和操作資料的函式從邏輯上組成一個整體(物件)
—>隱藏實作細節,暴露簡單的呼叫介面
3、繼承(inheritance):擴展已有的類創建新類,實作對已有類的代碼復用
4、多型(polymorphism):給不同的物件發出同樣的訊息,不同的物件執行了不同的行為,
—>方法重寫(override):子類對父類已有的方法,重新給出自己的實作版本
? 在重寫方法的程序中,不同的子類可以對父類的同一個方法給出不同的實作版本,
那么該方法在運行時就會表現出多型性
7 經典例題
例題一:
-
現在有三類員工:
- 部門經理:固定月薪,15000
- 程式員:計時結算月薪,
-
銷售員:底薪 + 提成,底薪1800,銷售額 %5提成
寫一個面向物件的編程實作工資的計算
from abc import abstractmethod
class Employee:
def __init__(self, name):
self.name = name
@abstractmethod
def get_salary(self):
pass
class Manager(Employee):
def get_salary(self):
return 15000
class Programmer(Employee):
def __init__(self, name):
super().__init__(name)
self.working_hour = 0
def get_salary(self):
return 200 * self.working_hour
class Salesman(Employee):
def __init__(self, name):
super().__init__(name)
self.sales = 0
def get_salary(self):
return 1800 + 0.05 * self.sales
def main():
emps = [Manager('劉備'), Programmer('諸葛亮'), Salesman('關羽')]
for emp in emps:
if type(emp) == Programmer:
emp.working_hour = int(input(f'請輸入{emp.name}本月作業時長'))
elif type(emp) == Salesman:
emp.sales = int(input(f'請輸入{emp.name}本月銷售額'))
print(f'{emp.name}本月工資:{emp.get_salary()}元')
if __name__ == '__main__':
main()
例題二:
# 創建一個時鐘物件(可以顯示時/分/秒),讓它運轉起來
import time
class Clock:
# 資料抽象
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.min = minute
self.sec = second
def show(self):
"""顯示時間"""
return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'
# 行為抽象
def run(self):
"""走字"""
self.sec += 1
if self.sec == 60:
self.sec = 0
self.min += 1
if self.min == 60:
self.min = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
if __name__ == '__main__':
clock = Clock()
while True:
print(clock.show())
time.sleep(1)
clock.run()
五、總結
? Python中的函式可以使用可變引數*args和關鍵字引數**kwargs來接收任意數量的引數,而且傳入引數時可以帶上引數名也可以沒有引數名,可變引數會被處理成一個元組,而關鍵字引數會被處理成一個字典,Python中的函式也是物件,所以函式可以作為函式的引數和回傳值,也就是說,在Python中我們可以使用高階函式,如果我們要定義的函式非常簡單,只有一行代碼且不需要名字,可以將函式寫成Lambda函式(匿名函式)的形式,
? 面向物件編程是一種非常流行的編程范式,除此之外還有指令式編程、函式式編程等編程范式,由于現實世界是由物件構成的,而物件是可以接收訊息的物體,所以面向物件編程更符合人類正常的思維習慣,類是抽象的,物件是具體的,有了類就能創建物件,有了物件就可以接收訊息,這就是面向物件編程的基礎,定義類的程序是一個抽象的程序,找到物件公共的屬性屬于資料抽象,找到物件公共的方法屬于行為抽象,
? 這周的函式和面向物件編程是重點,也是難點,需要反復的去鞏固練習,需要用時間去練習,以此來讓我們能夠熟練的掌握,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/292728.html
標籤:python
