Python基礎學習之深淺拷貝問題及遞歸函數練習

一、深淺拷貝問題

在實際工作中,經常涉及到數據的傳遞,在數據傳遞使用過程中,可能會發生數據被修改的問題。為瞭防止數據被修改,就需要在傳遞一個副本,即使副本被修改,也不會影響原數據的使用。為瞭生成這個副本,就產生瞭拷貝。下面先瞭解一下幾個概念:對象、可變類型、引用

Python對象:在 Python 中,對象有一種很通俗的說法是,萬物皆對象。說的就是構造的任何數據類型都是一個對象,無論是數字,字符串,還是函數,甚至是模塊,Python都對當做對象處理。所有 Python 對象都擁有三個屬性:身份、類型、值。看一個簡單的例子:

可變與不可變對象:在Python中,按更新對象的方式,可以將對象分為 2 大類:可變對象與不可變對象。

  • 可變對象: 列表、字典、集合,所謂可變是指可變對象的值可變,身份是不變的。
  • 不可變對象:數字、字符串、元組,不可變對象就是對象的身份和值都不可變。新創建的對象被關聯到原來的變量名,舊對象被丟棄,垃圾回收器會在適當的時機回收這些對象。

引用:在 Python 程序中,每個對象都會在內存中申請開辟一塊空間來保存該對象,該對象在內存中所在位置的地址被稱為引用。在開發程序時,所定義的變量名實際就對象的地址引用。

引用實際就是內存中的一個數字地址編號,在使用對象時,隻要知道這個對象的地址,就可以操作這個對象,但是因為這個數字地址不方便在開發時使用和記憶,所以使用變量名的形式來代替對象的數字地址。 在 Python 中,變量就是地址的一種表示形式,並不開辟開辟存儲空間。

就像 IP 地址,在訪問網站時,實際都是通過 IP 地址來確定主機,而 IP 地址不方便記憶,所以使用域名來代替 IP 地址,在使用域名訪問網站時,域名被解析成 IP 地址來使用。

通過一個例子來說明變量和變量指向的引用:

  • 基本類型和引用類型數據拷貝的問題。因為基本類型的數據大小是固定的,所以他保存在棧內存中;而引用類型的數據大小不固定,因而保存在堆內存中,單引用類型在棧內存中隻保存一個指向堆內存的指針。
  • 淺拷貝:對於淺拷貝來說,如果拷貝基本類型,那麼就等於賦值一樣,會直接拷貝其本身;但如果拷貝的是引用類型,就隻會拷貝一層,如果原對象發生改變,那麼拷貝對象也會發生改變。
  • 深拷貝:深拷貝的話就會拷貝多層,嵌套的對象也會被拷貝出來,相當於開辟一個新的內存地址用於存放拷貝的對象。在 python 語言中沒有明顯的指出,例如,操作指針或對於指針的操作。

  • 拷貝就是一個變量的值傳給另外一個變量。在 python 中 id() 方法可以查看存放變量的內存地址,這為我們下面理解深淺 copy 提供瞭便利。
  • 淺拷貝是指把存放變量的地址值傳給被賦值,最後兩個變量引用瞭同一份地址,如上圖所示。
  • 深拷貝是指被賦值的變量開辟瞭另一塊地址用來存放要賦值的變量的值(內容)。在 python 中引用 copy 模塊,copy模塊中有 deepcopy() 方法,調用它完成變量的深copy,觀察變量地址如下:

淺拷貝隻拷貝頂層引用,遇到引用類型,隻是復制瞭個引用,修改瞭副本中引用類型裡的數據,原數據也會改變,示例如下:

深拷貝會逐層進行拷貝,直到拷貝的所有引用都是不可變引用為止,示例如下:

lst1 = [1, [6, 7, 8], 3]
lst1
# [1, [6, 7, 8], 3]
lst2 = copy.deepcopy(lst1)
lst2
# [1, [6, 7, 8], 3]
lst2[1][1] = 996
print("副本:",  lst2)
print("原始:",  lst1)

# 副本: [1, [6, 996, 8], 3]
# 原始: [1, [6, 7, 8], 3]

在深拷貝中,修改瞭副本中引用類型裡的數據,原數據不會改變。

總結如下:

  1. Python默認的拷貝方式是淺拷貝:因為淺拷貝花費時間更少、花費內存更少、淺拷貝隻拷貝頂層數據,一般情況下比深拷貝效率高。大多數情況下,編寫程序時,都是使用淺拷貝,除非有特定的需求。
  2. Python中有多種方式實現淺拷貝,copy模塊的 copy 函數 ,對象的 copy 函數 ,工廠方法,切片等。
  3. 不可變對象在賦值時會開辟新空間;可變對象在賦值時,修改一個的值,另一個也會發生改變。深、淺拷貝對不可變對象拷貝時,不開辟新空間,相當於賦值操作。
  4. 淺拷貝在拷貝時,隻拷貝第一層中的引用,如果元素是可變對象,並且被修改,那麼拷貝的對象也會發生變化;深拷貝在拷貝時,會逐層進行拷貝,直到所有的引用都是不可變對象為止。

二、遞歸函數練習

1. 求階乘

def factorial(n):
    return 1 if n == 1 else n * factorial(n - 1)


factorial(5)

結果如下:

2. 猴子吃桃問題

猴子第一天摘下若幹個桃子,當即吃瞭一半,還不癮,又多吃瞭一個。第二天早上又將剩下的桃子吃掉一半,又多吃瞭一個。以後每天早上都吃瞭前一天剩的一半零一個。到第 10 天早上想再吃時,見隻剩下一個桃子瞭,求第一天共摘瞭多少桃子?

# 第10天早上想再吃時,見隻剩下一個桃子瞭,說明第9天的時候就隻剩一個桃子瞭
def eat_peach(n):
    return 1 if n == 2 else 2 * (eat_peach(n - 1) + 1)

eat_peach(10)

結果如下:

3. 打印斐波那契數列

# -*- coding: UTF-8 -*-
"""
@Author  :葉庭雲
@CSDN    :https://yetingyun.blog.csdn.net/
"""

def fibonacii(n):
    return n if n <= 1 else (fibonacii(n - 1) + fibonacii(n - 2))
        
x = int(input("輸出前幾項?: "))
if x <= 0:
    print("請輸入輸入正數!")
else:
    print("斐波那契數列前{}項:".format(x), end=" ")
    for i in range(1, x + 1):
        print(fibonacii(i), end=" ")

結果如下: 

到此這篇關於Python基礎學習之深淺拷貝問題及遞歸函數練習的文章就介紹到這瞭,更多相關Python 深淺拷貝 遞歸函數內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: