《Python核心技術與實戰》筆記2

Python對象的比較、拷貝

'==' VS 'is'

  • '=='操作符比較對象之間的值是否相等

  • 'is'操作符比較的是對象的身份標識是否相等,即它們是否是同一個對象,是否指向同一個內存地址

  • 每個對象的身份標識,都能通過函數 id(object) 獲得。因此,'is'操作符,相當於比較對象之間的 ID 是否相等

  • a = 10
    b = 10 
    a == b
    True
    id(a)
    4427562448
    id(b)
    4427562448
    a is b
    True
    
  • 需要注意,對於整型數字來說,以上a is b爲 True 的結論,只適用於 -5 到 256 範圍內的數字

    • 出於對性能優化的考慮,Python 內部會對 -5 到 256 的整型維持一個數組,起到一個緩存的作用。這樣,每次你試圖創建一個 -5 到 256 範圍內的整型數字時,Python 都會從這個數組中返回相對應的引用,而不是重新開闢一塊新的內存空間
  • 比較操作符'is'的速度效率,通常要優於'=='。因爲'is'操作符不能被重載

淺拷貝和深度拷貝

copy.copy()

  • 對於可變序列,通過:完成淺拷貝

  • 需要注意的是,對於元組,使用 tuple() 或者切片操作符':'不會創建一份淺拷貝,相反,它會返回一個指向相同元組的引用

  • 淺拷貝,是指重新分配一塊內存,創建一個新的對象,裏面的元素是原對象中子對象的引用。因此,如果原對象中的元素不可變,那倒無所謂;但如果元素可變,淺拷貝通常會帶來一些副作用,尤其需要注意。

  • l1 = [[1, 2], (30, 40)]
    l2 = list(l1)
    l1.append(100)
    l1[0].append(3)
     
    l1
    [[1, 2, 3], (30, 40), 100]
     
    l2
    [[1, 2, 3], (30, 40)]
     
    l1[1] += (50, 60)
    l1
    [[1, 2, 3], (30, 40, 50, 60), 100]
     
    l2
    [[1, 2, 3], (30, 40)]
    

copy.deepcopy()

import copy
l1 = [[1, 2], (30, 40)]
l2 = copy.deepcopy(l1)
l1.append(100)
l1[0].append(3)
 
l1
[[1, 2, 3], (30, 40), 100]
 
l2 
[[1, 2], (30, 40)]
  • 深度拷貝也不是完美的,往往也會帶來一系列問題。如果被拷貝對象中存在指向自身的引用,那麼程序很容易陷入無限循環:

  • import copy
    x = [1]
    x.append(x)
     
    x
    [1, [...]]
     
    y = copy.deepcopy(x)
    y
    [1, [...]]
    

值傳遞,引用傳遞or其他

重要

Python 變量及其賦值

  • Python 的數據類型,例如整型(int)、字符串(string)、元組等等,是不可變的。所以,a = a + 1,並不是讓 a 的值增加 1,而是表示重新創建了一個新的值爲 2 的對象,並讓 a 指向它。

  • 需要注意的是,Python 裏的變量可以被刪除,但是對象無法被刪除。比如下面的代碼:

    • l = [1, 2, 3]
      del l
      
    • del l 刪除了 l 這個變量,從此以後你無法訪問 l,但是對象 [1, 2, 3] 仍然存在。Python 程序運行時,其自帶的垃圾回收系統會跟蹤每個對象的引用。如果 [1, 2, 3] 除了 l 外,還在其他地方被引用,那就不會被回收,反之則會被回收。

    • 變量的賦值,只是表示讓變量指向了某個對象,並不表示拷貝對象給變量;而一個對象,可以被多個變量所指向。
    • 可變對象(列表,字典,集合等等)的改變,會影響所有指向該對象的變量。
    • 對於不可變對象(字符串,整型,元祖等等),所有指向該對象的變量的值總是一樣的,也不會改變。但是通過某些操作(+= 等等)更新不可變對象的值時,會返回一個新的對象。
    • 變量可以被刪除,但是對象無法被刪除。

Python 函數的參數傳遞

“Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per Se.”

Python 的參數傳遞是賦值傳遞 (pass by assignment),或者叫作對象的引用傳遞(pass by object reference)。Python 裏所有的數據類型都是對象,所以參數傳遞時,只是讓新變量與原變量指向相同的對象而已,並不存在值傳遞或是引用傳遞一說。

def my_func1(b):
	b = 2
 
a = 1
my_func1(a)
a
1

def my_func2(b):
	b = 2
	return b
 
a = 1
a = my_func2(a)
a
2

def my_func3(l2):
	l2.append(4)
 
l1 = [1, 2, 3]
my_func3(l1)
l1
[1, 2, 3, 4]

def my_func4(l2):
	l2 = l2 + [4]
 
l1 = [1, 2, 3]
my_func4(l1)
l1
[1, 2, 3]

def my_func5(l2):
	l2 = l2 + [4]
	return l2
 
l1 = [1, 2, 3]
l1 = my_func5(l1)
l1
[1, 2, 3, 4]

裝飾器

#簡單
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
 
@my_decorator
def greet():
    print('hello world')
 
greet()

# 帶參數
def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper
 
@my_decorator
def greet(message):
    print(message)
 
greet('hello world')
 
# 輸出
wrapper of decorator
hello world

# 通常情況下,我們會把*args和**kwargs,作爲裝飾器內部函數 wrapper() 的參數。*args和**kwargs,表示接受任意數量和類型的參數
import functools
def my_decorator(func):
    @functools.wraps(func) # 保留原函數的元信息
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(message):
    print(message)
 
greet.__name__
 
# 輸出
'greet'

裝飾器,其實就是通過裝飾器函數,來修改原函數的一些功能,使得原函數不需要修改。

Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

而實際工作中,裝飾器通常運用在身份認證、日誌記錄、輸入合理性檢查以及緩存等多個領域中。

深入理解迭代器和生成器

列表(list: [0, 1, 2]),元組(tuple: (0, 1, 2)),字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器。所有的容器都是可迭代的(iterable)。

迭代器(iterator)提供了一個 next 的方法。調用這個方法後,你要麼得到這個容器的下一個對象,要麼得到一個 StopIteration 的錯誤。

可迭代對象,通過 iter() 函數返回一個迭代器,再通過 next() 函數就可以實現遍歷。for in 語句將這個過程隱式化。

協程

併發Futures 和 Asyncio

  • 併發,通過線程和任務之間互相切換的方式實現,但同一時刻,只允許有一個線程或任務執行。
  • 而並行,則是指多個進程完全同步同時的執行。

GIL(全局解釋器鎖Global Interpreter Lock)

  • 每一個 Python 線程,在 CPython 解釋器中執行時,都會先鎖住自己的線程,阻止別的線程執行。

  • CPython 引進 GIL 其實主要就是這麼兩個原因:

    • 一是設計者爲了規避類似於內存管理這樣的複雜的競爭風險問題(race condition);
    • 二是因爲 CPython 大量使用 C 語言庫,但大部分 C 語言庫都不是原生線程安全的(線程安全會降低性能和增加複雜度)。
  • GIL 的設計,主要是爲了方便 CPython 解釋器層面的編寫者,而不是 Python 應用層面的程序員。作爲 Python 的使用者,我們還是需要 lock 等工具,來確保線程安全。

  • n = 0
    lock = threading.Lock()
     
    def foo():
        global n
        with lock:
            n += 1
    

垃圾回收機制

Python 中一切皆對象;

垃圾回收是 Python 自帶的機制,用於自動釋放不會再用到的內存空間;

import sys
a = []
 
# 兩次引用,一次來自 a,一次來自 getrefcount
print(sys.getrefcount(a))
 
def func(a):
    # 四次引用,a,python 的函數調用棧,函數參數,和 getrefcount
    print(sys.getrefcount(a))
 
func(a)
 
# 兩次引用,一次來自 a,一次來自 getrefcount,函數 func 調用已經不存在
print(sys.getrefcount(a))
 
########## 輸出 ##########
 
2
4
2
# 手動回收
import gc
a = []
del a
gc.collect()
  • Python 使用標記清除(mark-sweep)算法和分代收集(generational),來啓用針對循環引用的自動垃圾回收;
  • 引用計數是其中最簡單的實現,不過切記,這只是充分非必要條件,因爲循環引用需要通過不可達判定,來確定是否可以回收;
  • 調試內存泄漏方面, objgraph 是很好的可視化分析工具;

加餐

裝飾器的作用與意義,在於其可以通過自定義的函數或類,在不改變原函數的基礎上,改變原函數的一些功能。

Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

  • 代碼更加簡潔;
  • 邏輯更加清晰;
  • 程序的層次化、分離化更加明顯。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章