Python函數進階與文件操作詳情

一、作業回顧

1、格式化輸出與%百分號

以下結果中,可以正常輸出“50%及格”語句是(B)

A、print(“%d%及格” % (50)) => 回答大部分結果(Python這種寫法不正確)

B、print(“%d%%及格” % (50)) => 正確結果

2、字符串切片

定義一個字符串str1 = ‘abcdefg’,使用切片截取字符串str1[3::-2],求返回結果:(C)

C、‘db’

3、字典的定義

其實字典中的key可以是很多數據類型(不可變數據類型 => 整型、浮點型、字符串、元組)

my_dict = {}
my_dict[1] = 1
my_dict['1'] = 2
my_dict[1.0] = 3
print(my_dict[1] + my_dict['1'] + my_dict[1.0])

二、引用變量與可變、非可變類型

1、引用變量

在大多數編程語言中,值的傳遞通常可以分為兩種形式“值傳遞與引用傳遞”,但是在Python中變量的傳遞基本上都是引用傳遞。

聊聊變量在內存底層的存儲形式

a = 10

第一步:首先在計算機內存中創建一個數值10(占用一塊內存空間)

第二步:在棧空間中聲明一個變量,如a

第三步:把數值10的內存地址賦予給變量小a,形成所謂的==“引用關系”==

如何驗證Python中變量的引用關系

答:可以使用內置方法id(),其參數就是要顯示的變量信息 => id(變量名稱)

a = 10
print(id(a))

把一個變量賦予給另外一個變量的影響

a = 10
b = a
print(id(a))
print(id(b))

運行結果:

說明:由以上運行結果可知,當我們把一個變量賦予給另外一個變量時,其兩者指向的內存地址相同。

就說明a和b指向瞭同一塊內存空間,原理圖如下:

思考:如果在b = a以後,我們改變瞭變量a的值,問變量b是否會受到影響?

# a = 10
# print(id(a))

a = 10
b = a

a = 100
print(b)  # 10 或 100

print(id(a))
print(id(b))

原理圖:

總結:不可變數據類型(數值)在賦值以後,其中一個值的改變不影響另外一個變量,因為兩者指向空間地址不同。

2、Python中可變和非可變數據類型

 問題1:在Python中一共有幾種數據類型?

答:7種,數值(int整型、float浮點類型)、bool類型(True和False)、字符串類型(str)、元組(tuple 1,2,3)、列表(list [1, 2, 3])、字典(dict {key:value})、集合(set {1, 2})

在Python中,我們可以把7種數據類型分為兩大類:可變類型 + 非可變類型

① 非可變類型

數值(int整型、float浮點類型)

bool類型(True和False)

字符串類型(str)

元組(tuple 1,2,3)

② 可變類型

列表(list [1, 2, 3])

字典(dict {key:value})

集合(set {1, 2})

問題2:如何判斷一個數據類型是可變類型還是非可變類型?

在Python中,可變類型與非可變類型主要是通過這個數據類型在內存中的表現形式來進行定義的。

① 可變類型就是在內存中,其內存地址一旦固定,其值是可以發生改變的

a = [1, 2, 3]
print(id(a))

# 向內存中追加新數據(對數據進行改變隻能通過數據類型.方法()實現)
a.append(4)
print(id(a))

原理圖:

② 非可變類型就是在內存中,內存地址一旦固定,其值就沒辦法發生任何改變瞭

a = 10
print(id(a))

a = 'hello'
print(id(a))

原理圖:

3、可變類型與非可變類型在函數中的應用

可變類型

# 定義一個函數
def func(names):
    print(names)
    
# 定義一個全局變量
names = ['張三', '李四', '王五']
# 調用函數
func(names)

原理圖:

綜上所述:可變類型在函數中,如果在全局或局部中對可變類型進行增刪改操作,其外部和內部都會受到影響。

不可變類型

# 定義一個函數
def func(num):
    num += 1
    print(num)
    
# 定義一個全局變量
a = 10
# 調用函數
func(a)
# 在全局作用域中打印a
print(a)

綜上所述:不可變類型在函數中,局部或全局的改變對外部和內部都沒有任何影響。

三、函數遞歸(重點難點)

1、前言

編程思想:如何利用數學模型,來解決對應的需求問題;然後利用代碼實現對應的數據模

算法:使用代碼實現對應的數學模型,從而解決對應的業務問題

程序 = 算法 + 數據結構

在我們經常使用的算法中,有兩種非常常用的算法:遞推算法 + 遞歸算法,專門用於解決一些比較復雜,但是拆分後相似度又非常高的程序。

2、遞推算法

遞歸算法:遞推算法是一種簡單的算法,即通過已知條件,利用特定條件得出中間推論,直至得到結果的算法。遞推又分為順推和逆推。

順推:通過最簡單的條件,然後逐步推演結果

逆推:通過結果找到規律,然後推導已知條件

遞推算法案例:斐波那契數列

1 1 2 3 5 8 13 21 …

① ② ③ ④ ⑤ ⑥ …

第1位為1,第2位為1,第3位為2 = 1 + 1,第4位為3 = 2 + 1,依次類推…第n位結果為多少?

f(n) = f(n-1) + f(n-2)

提出問題:求斐波那契數列第15位的結果?

分析:f(15) = f(14) + f(13)

​ f(14) = f(13) + f(12)

​ f(13) = f(12) + f(11)

​ …

​ f(4) = f(3) + f(2) = 3 + 1

​ f(3) = f(2) + f(1) = 2

​ f(2) = 1

​ f(1) = 1

遞推算法:使用while循環或for循環

# 遞推算法:根據已知條件,求結果(或者根據結果求未知條件)
def recusive(n):
    """ 返回斐波那契數列某一位(n>=1)的結果 """
    if n == 1 or n == 2:
        return 1
    # 開始遞推f(3) = f(2) + f(1)  f(4) = f(3) + f(2) ... f(15) = f(14) + f(13)
    dict1 = {1:1, 2:1}
    for i in range(3, n+1):
        # f(3) = f(2) + f(1)
        # f(i) = f(i-1) + f(i-2)
        dict1[i] = dict1[i-1] + dict1[i-2]
    return dict1[n]

# 函數調用
print(recusive(15))

3、什麼是遞歸算法

程序調用自身的編程技巧稱為遞歸( recursion)。遞歸做為一種算法在程序設計語言中廣泛應用,它通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞歸策略隻需少量的程序就可描述出解題過程所需要的多次重復計算,大大地減少瞭程序的代碼量。

① 簡化問題:找到最優子問題(不能再小) ② 函數自己調用自己

def func():
    # 自己調用自己
    func()
    
func()

4、遞歸兩種重要的元素

遞歸有兩個非常重要的概念:

① 遞歸點:找到解決當前問題的等價函數(先解決規模比當前問題小一些的函數,依次類推,最終實現對問題的解決) => 有遞有歸

② 遞歸出口:當問題解決的時候,已經到達(必須存在)最優問題,不能再次調用函數瞭

註:如果一個遞歸函數沒有遞歸出口就變成瞭死循環

5、編寫遞歸三步走

① 明確你這個函數想要幹什麼

如:求斐波那契數列

② 尋找遞歸結束條件

如:就是在什麼情況下,遞歸會停止循環,返回結果

③ 找出函數的等價關系式

如:斐波那契數列,第n位 f(n) = f(n-1) + f(n-2)

案例1:使用遞歸求斐波那契數列

第一步:明確這個函數想要幹什麼(先定義出來,明確調用方式)

# 斐波那契數列 1 1 2 3 5 8 13 21 ...
def f(n):
    # 編寫遞歸代碼求第n位的結果

# 調用函數
print(f(15))  # 610

第二步:尋找遞歸的結束條件

# 斐波那契數列 1 1 2 3 5 8 13 21 ...
def f(n):
    # 編寫遞歸代碼求第n位的結果
    if n == 1 or n == 2:
        return 1

# 調用函數
print(f(15))  # 610

第三步:找出函數的等價關系式(最關鍵的一步)

# 斐波那契數列 1 1 2 3 5 8 13 21 ...
def f(n):
    # 編寫遞歸代碼求第n位的結果
    if n == 1 or n == 2:
        return 1
    # 找出與斐波那契數列等價的關系式
    return f(n-1) + f(n-2)

# 調用函數
print(f(15))  # 610

案例2:使用遞歸求N的階乘(如n=100)

階乘是什麼?一個正整數的階乘(factorial)是所有小於及等於該數的正整數的積,如:n!=1×2×3×…×(n-1)×n

1! = 1

2! = 1×2 = 2

3! = 1x2x3 = 6

4! = 1x2x3x4 = 24

n!=1×2×3×…×(n-1)×n

第一步:明確這個函數要做什麼以及定義函數以及調用方式

def f(n):
    # 編寫遞歸條件
    
print(f(100))

第二步:尋找遞歸的結束條件

def f(n):
    # 編寫遞歸結束條件
    if n <= 2:
        return n
    # ...遞歸等式
print(f(100))

第三步:編寫遞歸等價公式(自己要調用自己)

等價公式 = 找規律

1! = f(1) = 1

2! = f(2) = 2

3! = f(2)x3 = 6

4! = f(3)x4 = 24

n!= f(n-1) * n

def f(n):
    # 編寫遞歸結束條件
    if n <= 2:
        return n
    # ...遞歸等式
    return f(n-1) * n
print(f(100))

案例3:面試題 => 猴子吃桃問題

猴子吃桃問題。猴子第1天摘下若幹個桃子,當即吃瞭一半,還不過癮,又多吃瞭一個。第2天早上又將剩下的桃子吃掉一半,又多吃瞭一個。以後每天早上都吃瞭前一天剩下的一半另加一個。到第10天早上想再吃時,就隻剩下一個桃子瞭。求第1天共摘瞭多少個桃子

第一步:確定函數主要要完成什麼功能,需要傳遞哪些參數,確認調用方式

def f(n):
    # 編寫遞歸代碼

# 調用f函數
print(f(1))

第二步:編寫遞歸的結束條件(出口)

# 第一步:確定函數功能
def f(n):
    # 第二步:編寫遞歸結束條件(出口)
    if n == 10:
        return 1

# 調用函數
print(f(1))

第三步:找出與這個問題相等的等式關系

求桃子的剩餘數量?假設法:假設有10個桃子

第1天,10個桃子吃一半,10/2 = 5 + 1 = 6

第2天,4個桃子吃一半,4/2 = 2 + 1 = 3

第3天,再想吃剩1個

第n天,總剩餘桃子的數量 = (第(n+1)天桃子的剩餘桃子的數量 + 1) * 2

# 第一步:確定函數功能
def f(n):
    # 第二步:編寫遞歸結束條件(出口)
    if n == 10:
        return 1
    # 第三步:尋找與這個問題相似的等價公式
    return (f(n+1) + 1) * 2

# 調用函數
print(f(8))

四、lambda表達式

1、普通函數與匿名函數

在Python中,函數是一個被命名的、獨立的完成特定功能的一段代碼,並可能給調用它的程序一個返回值。

所以在Python中,函數大多數是有名函數 => 普通函數。但是有些情況下,我們為瞭簡化程序代碼,也可以定義匿名函數 => lambda表達式

2、lambda表達式應用場景

如果一個函數有一個返回值,並且隻有一句代碼,可以使用 lambda簡化。

3、lambda表達式基本語法

變量 = lambda 函數參數:表達式(函數代碼 + return返回值)
# 調用變量
變量()

4、編寫lambda表達式

定義一個函數,經過一系列操作,最終返回100

def fn1():
    return 100

# 調用fn1函數
print(fn1)  # 返回fn1函數在內存中的地址
print(fn1())  # 代表找到fn1函數的地址並立即執行

lambda表達式進行簡化:

fn2 = lambda : 100

print(fn2)  # 返回fn2在內存中的地址
print(fn2())

5、編寫帶參數的lambda表達式

編寫一個函數求兩個數的和

def fn1(num1, num2):
    return num1 + num2

print(fn1(10, 20))

lambda表達式進行簡化:

fn2 = lambda num1, num2:num1 + num2

print(fn2(10, 20))

6、lambda表達式相關應用

帶默認參數的lambda表達式

fn = lambda a, b, c=100 : a + b + c
print(fn(10, 20))

不定長參數:可變參數*args

fn1 = lambda *args : args

print(fn1(10, 20, 30))

不定長參數:可變參數**kwargs

fn2 = lambda **kwargs : kwargs

print(fn2(name='Tom', age=20, address='北京市海淀區'))

帶if判斷的lambda表達式

fn = lambda a, b : a if a > b else b

print(fn(10, 20))

列表數據+字典數據排序(重點)

知識點:列表.sort(key=排序的key索引, reverse=True)

students = [
    {'name': 'Tom', 'age': 20},
    {'name': 'Rose', 'age': 19},
    {'name': 'Jack', 'age': 22}
]

# 按name值升序排列
students.sort(key=lambda x: x['name'])
print(students)

# 按name值降序排列
students.sort(key=lambda x: x['name'], reverse=True)
print(students)

# 按age值升序排列
students.sort(key=lambda x: x['age'])
print(students)

執行流程:

students = [
    {'name': 'Tom', 'age': 20},
    {'name': 'Rose', 'age': 19},
    {'name': 'Jack', 'age': 22}
]

# 按name值升序排列
students.sort(key=lambda x:x['name'])
print(students)

五、Python中高階函數

1、什麼是高階函數

把函數作為參數傳入,這樣的函數稱為高階函數,高階函數是函數式編程的體現。函數式編程就是指這種高度抽象的編程范式。

2、高階函數的由來

在Python中,abs()函數可以完成對數字求絕對值計算。

① 正數的絕對值是它本身 ② 負數的絕對值是它的相反數

abs()返回的結果都是正數

abs(-10) # 10

round()函數可以完成對數字的四舍五入計算。

round(1.2)  # 1
round(1.9)  # 2

需求:任意兩個數字,按照指定要求(① 絕對值 ② 四舍五入)整理數字後再進行求和計算。

def fn1(num1, num2):
    return abs(num1) + abs(num2)

print(fn1(-10, 10))
def fn2(num1, num2):
    return round(num1) + round(num2)

print(fn2(10.2, 6.9))

要求:我們能不能對以上進行簡化,然後合並為同一個函數 => 設計思想(高階函數)

def fn(num1, num2, f):
    # f代表要傳入的參數(參數是一個函數名,如abs或round)
    return f(num1) + f(num2)

# 絕對值求和
print(fn(-10, 10, abs))
# 四舍五入
print(fn(10.2, 6.9, round))

3、map()函數

map(func, lst),將傳入的函數變量func作用到lst變量的每個元素中,並將結果組成新的列表(Python2)/迭代器(Python3)返回。

lst = [1, 2, 3]

func函數:求某個數的平方,如輸入2返回4,輸入3返回9

map(func, lst)返回結果[1, 4, 9]

# 定義一個函數
def func(n):
    return n ** 2
# 定義一個列表
list1 = [1, 2, 3]
# 使用map對lst進行func函數操作
list2 = list(map(func, list1))
print(list2)

4、reduce()函數

reduce(func,lst),其中func必須有兩個參數。每次func計算的結果繼續和序列的下一個元素做累加計算。> 註意:reduce()傳入的參數func必須接收2個參數。

list1 = [1, 2, 3]

def func(a, b):

​ return a + b

reduce(func,lst)則把列表中的每個元素放入func中進行加工,然後進行累加操作

import functools
# 定義一個函數
def func(a, b):
    return a + b
# 定義一個列表
list1 = [10, 20, 30, 40, 50]
sums = functools.reduce(func, list1)
print(sums)

5、filter()函數

filter(func, lst)函數用於過濾序列, 過濾掉不符合條件的元素, 返回一個 filter 對象。如果要轉換為列表, 可以使用 list() 來轉換。

# 定義一個函數(獲取所有的偶數)
def func(n):
   return n % 2 == 0
# 定義一個序列
list1 = [1, 2, 3, 4, 5, 6, 7, 8]
# 調用filter函數進行過濾操作
result = filter(func, list1)
print(list(result))

六、文件的概念

1、什麼是文件

內存中存放的數據在計算機關機後就會消失。要長久保存數據,就要使用硬盤、光盤、U 盤等設備。為瞭便於數據的管理和檢索,引入瞭==“文件”==的概念。

一篇文章、一段視頻、一個可執行程序,都可以被保存為一個文件,並賦予一個文件名。操作系統以文件為單位管理磁盤中的數據。一般來說,文件可分為文本文件、視頻文件、音頻文件、圖像文件、可執行文件等多種類別。

2、思考:文件操作包含哪些內容呢?

在日常操作中,我們對文件的主要操作:創建文件、打開文件、文件讀寫、文件備份等等

3、文件操作的作用

文件操作的作用就是把一些內容(數據)存儲存放起來,可以讓程序下一次執行的時候直接使用,而不必重新制作一份,省時省力。

七、文件的基本操作

1、文件操作三步走

① 打開文件

② 讀寫文件

③ 關閉文件

2、open函數打開文件

在Python,使用open()函數,可以打開一個已經存在的文件,或者創建一個新文件,語法如下:

f = open(name, mode)
註:返回的結果是一個file文件對象(後續會學習,隻需要記住,後續方法都是f.方法())

name:是要打開的目標文件名的字符串(可以包含文件所在的具體路徑)。

mode:設置打開文件的模式(訪問模式):隻讀r、寫入w、追加a等。

r模式:代表以隻讀模式打開一個已存在的文件,後續我們對這個文件隻能進行讀取操作。如果文件不存在,則直接報錯。另外,r模式在打開文件時,會將光標放在文件的一行。

w模式:代表以隻寫模式打開一個文件,文件不存在,則自動創建該文件。w模式主要是針對文件寫入而定義的模式。但是,要特別註意,w模式在寫入時,光標也是置於第一行同時還會清空原有文件內容。

a模式:代表以追加模式打開一個文件,文件不存在,則自動創建該文件。a模式主要也是針對文件寫入而定義模式。但是和w模式有所不同,a模式不會清空文件的原有內容,而是在文件的尾部追加內容。

3、write函數寫入文件

基本語法:

f.write('要寫入的內容,要求是一個字符串類型的數據')

4、close函數關閉文件

f.close()

5、入門級案例

# 1、打開文件
f = open('python.txt', 'w')
# 2、寫入內容
f.write('人生苦短,我學Python!')
# 3、關閉文件
f.close()

強調一下:中文亂碼問題,默認情況下,計算機常用編碼ASCII、GBK、UTF-8

6、解決寫入中文亂碼問題

# 1、打開文件
f = open('python.txt', 'w', encoding='utf-8')
# 2、寫入內容
f.write('人生苦短,我學Python!')
# 3、關閉文件
f.close()

7、文件的讀取操作

read(size)方法:主要用於文本類型或者二進制文件(圖片、音頻、視頻…)數據的讀取

size表示要從文件中讀取的數據的長度(單位是字節),如果沒有傳入size,那麼就表示讀取文件中所有的數據。

f.read()  # 讀取文件的所有內容
f.read(1024)  # 讀取1024個字節長度文件內容,字母或數字,一個占1個字節長度。中文utf-8占3個字節長度。
# 1、打開文件
f = open('python.txt', 'r', encoding='utf-8')
# 2、使用read()方法讀取文件所有內容
contents = f.read()
print(contents)
# 3、關閉文件
f.close()

readlines()方法:主要用於文本類型數據的讀取

readlines可以按照行的方式把整個文件中的內容進行一次性讀取,並且返回的是一個列表,其中每一行的數據為一個元素。

# 1、打開文件
f = open('python.txt', 'r', encoding='utf-8')
# 2、讀取文件
lines = f.readlines()
for line in lines:
    print(line, end='')
# 3、關閉文件
f.close()

8、聊聊文件操作的mode模式

模式 描述
r 以隻讀方式打開文件。文件的指針將會放在文件的開頭。這是默認模式。
rb 以二進制格式打開一個文件用於隻讀。文件指針將會放在文件的開頭。這是默認模式。
r+ 打開一個文件用於讀寫。文件指針將會放在文件的開頭。
rb+ 以二進制格式打開一個文件用於讀寫。文件指針將會放在文件的開頭。
w 打開一個文件隻用於寫入。如果該文件已存在則打開文件,並從開頭開始編輯,即原有內容會被刪除。如果該文件不存在,創建新文件。
wb 以二進制格式打開一個文件隻用於寫入。如果該文件已存在則打開文件,並從開頭開始編輯,即原有內容會被刪除。如果該文件不存在,創建新文件。
w+ 打開一個文件用於讀寫。如果該文件已存在則打開文件,並從開頭開始編輯,即原有內容會被刪除。如果該文件不存在,創建新文件。
wb+ 以二進制格式打開一個文件用於讀寫。如果該文件已存在則打開文件,並從開頭開始編輯,即原有內容會被刪除。如果該文件不存在,創建新文件。
a 打開一個文件用於追加。如果該文件已存在,文件指針將會放在文件的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該文件不存在,創建新文件進行寫入。
ab 以二進制格式打開一個文件用於追加。如果該文件已存在,文件指針將會放在文件的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該文件不存在,創建新文件進行寫入。
a+ 打開一個文件用於讀寫。如果該文件已存在,文件指針將會放在文件的結尾。文件打開時會是追加模式。如果該文件不存在,創建新文件用於讀寫。
ab+ 以二進制格式打開一個文件用於追加。如果該文件已存在,文件指針將會放在文件的結尾。如果該文件不存在,創建新文件用於讀寫。

雖然mode文件操作模式很多,但是我們隻需要記住3個字符即可。r、w、a

r+、w+、a+,代加號,功能全,既能讀,又能寫(區別在於指針到底指向不同)

rb、wb、ab,代b的字符,代表以二進制的形式對其進行操作,適合讀取文本或二進制格式文件,如圖片、音頻、視頻等格式

rb+、wb+、ab+,代加號,功能全,既能讀,又能寫(區別在於指針到底指向不同)

9、seek函數移動光標

無論是文件讀操作,還是寫操作。其起始位置都是文件光標決定的。

r => 文件頭

w => 清空文件內容,指向文件頭

a => 文件尾

光標在剛打開文件時,默認情況下是根據r、w、a模式相關固定的。但是我們可以通過某些方法,人為移動光標。可以通過seek方法實現。

f.seek(offset,whence=0)

offset:開始的偏移量,也就是代表需要移動偏移的字節數
whence:給offset參數一個定義,表示要從哪個位置開始偏移;0代表從文件開頭開始算起,1代表從當前位置開始算起,2代表從文件末尾算起

實際工作中,seek主要用於重置光標到起始位置。

f.seek(0)
#或
f.seek(0, 0)

其他應用:

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # 從0開始向右移動5個字節
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # 從右向左移動3個字節
13
>>> f.read(1)
b'd'

八、文件備份案例

1、案例需求

需求:用戶輸入當前目錄下任意文件名,完成對該文件的備份功能(備份文件名為xx[備份]後綴,例如:test[備份].txt)。

實現思路:

① 接收用戶輸入的文件名

② 規劃備份文件名

③ 備份文件寫入數據

2、代碼實現

# 1、接收用戶輸入的文件名(要備份的文件名)
oldname = input('請輸入要備份的文件名稱:')  # python.txt
# 2、規劃備份文件名(python[備份].txt)
# 搜索點號
index = oldname.rfind('.')
# 返回文件名和文件後綴
name = oldname[:index]
postfix = oldname[index:]
newname = name + '[備份]' + postfix
# 3、對文件進行備份操作
old_f = open(oldname, 'rb')
new_f = open(newname, 'wb')

# 讀取源文件內容寫入新文件
while True:
    content = old_f.read(1024)
    if len(content) == 0:
        break
    new_f.write(content)
# 4、關閉文件
old_f.close()
new_f.close()

到此這篇關於Python函數進階與文件操作詳情的文章就介紹到這瞭,更多相關Python文件操作 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: