福永網(wǎng)站設(shè)計(jì)多少錢seo入門免費(fèi)教程
1. 可變對(duì)象和不可變對(duì)象
(1). 不可變對(duì)象( Immutable Objects
)
不可變對(duì)象指的是那些一旦創(chuàng)建后其內(nèi)容就不能被修改的對(duì)象。如果嘗試修改不可變對(duì)象的內(nèi)容,將會(huì)創(chuàng)建一個(gè)新的對(duì)象而不是修改原來(lái)的對(duì)象。常見(jiàn)的不可變類型包括:
- 數(shù)字類型:
int, float, complex
- 字符串:
str
- 元組:
tuple
- 凍結(jié)集合:
frozenset
可以理解為對(duì)象的內(nèi)容一旦修改(改變),對(duì)象的內(nèi)存地址改變
s = "Hello"
print(id(s)) # 輸出原始字符串的內(nèi)存地址s += ", World!"
print(id(s)) # 輸出新字符串的內(nèi)存地址,與之前不同
(2). 可變對(duì)象
可變對(duì)象是指在其創(chuàng)建之后還可以對(duì)其內(nèi)容進(jìn)行修改的對(duì)象。這意味著你可以在不改變對(duì)象身份的情況下更改它的內(nèi)容。常見(jiàn)的可變類型包括:
列表: list
字典: dict
集合: set
**自定義類實(shí)例:**除非特別設(shè)計(jì)為不可變
lst = [1, 2, 3]
print(id(lst)) # 輸出列表的內(nèi)存地址
lst.append(4)
print(id(lst)) # 內(nèi)存地址保持不變,說(shuō)明是原地修改
2. @staticmethod和@classmethod
(1). @staticmethod
定義:使用@staticmethod
裝飾的方法不接受隱式的第一個(gè)參數(shù)(如self
或cls
)。這意味著這些方法既不能訪問(wèn)實(shí)例屬性也不能訪問(wèn)類屬性。
用途:通常用于那些與類有關(guān)但不需要訪問(wèn)類或?qū)嵗齼?nèi)部數(shù)據(jù)的功能。 這樣的方法更像是普通的函數(shù),只是由于組織上的原因被放在了類中。
特點(diǎn):
- 不能訪問(wèn)實(shí)例屬性: 由于靜態(tài)方法沒(méi)有self參數(shù),所以無(wú)法訪問(wèn)任何與特定對(duì)象實(shí)例相關(guān)的屬性。
- 不能訪問(wèn)類屬性: 同樣地,因?yàn)闆](méi)有cls參數(shù),靜態(tài)方法也無(wú)法直接訪問(wèn)類級(jí)別的屬性。
class Example:class_var = "I am a class variable" # 類變量def __init__(self, value):self.instance_var = value # 實(shí)例變量@staticmethoddef static_method():# 下面這兩行會(huì)導(dǎo)致錯(cuò)誤,因?yàn)殪o態(tài)方法無(wú)法訪問(wèn)實(shí)例或類屬性# print(self.instance_var) # AttributeError: 'staticmethod' object has no attribute 'instance_var'# print(class_var) # NameError: name 'class_var' is not definedprint("This is a static method.")def instance_method(self):print(f"Instance variable: {self.instance_var}")print(f"Class variable: {Example.class_var}")# 創(chuàng)建實(shí)例
ex = Example("I am an instance variable")# 調(diào)用靜態(tài)方法
Example.static_method() # 輸出: This is a static method.# 調(diào)用實(shí)例方法
ex.instance_method()
# 輸出:
# Instance variable: I am an instance variable
# Class variable: I am a class variable
在這個(gè)例子中,my_static_method
是一個(gè)靜態(tài)方法,它可以直接通過(guò)類名調(diào)用,不需要?jiǎng)?chuàng)建類的實(shí)例。
(2). @classmethod
定義: 使用@classmethod
裝飾的方法接收一個(gè)隱含的第一個(gè)參數(shù),這個(gè)參數(shù)通常是cls,代表類本身。因此,類方法可以訪問(wèn)和修改類級(jí)別的屬性,也可以調(diào)用其他類方法。
用途: 常用于需要操作類級(jí)別數(shù)據(jù)的方法,或者當(dāng)你需要從該方法返回類的不同子類時(shí)很有用。
示例 1: 訪問(wèn)和修改類級(jí)別屬性
假設(shè)我們有一個(gè)Person
類,其中包含一個(gè)類級(jí)別的屬性count
,用于記錄創(chuàng)建了多少個(gè)Person
對(duì)象。我們可以使用類方法來(lái)更新這個(gè)計(jì)數(shù)器。
class Person:count = 0 # 類變量,用于跟蹤創(chuàng)建了多少個(gè)Person對(duì)象def __init__(self, name):self.name = namePerson.count += 1 # 每當(dāng)創(chuàng)建一個(gè)新的實(shí)例時(shí)增加計(jì)數(shù)@classmethoddef get_count(cls):return cls.count # 使用cls訪問(wèn)類變量# 創(chuàng)建一些Person實(shí)例
p1 = Person("Alice")
p2 = Person("Bob")
# 使用類方法獲取當(dāng)前的計(jì)數(shù)
print(Person.get_count()) # 輸出應(yīng)該是2,因?yàn)閯?chuàng)建了兩個(gè)實(shí)例
在這個(gè)例子中,get_count
是一個(gè)類方法,它可以通過(guò)cls
訪問(wèn)類級(jí)別的屬性count
,而無(wú)需實(shí)例化Person
類。
示例 2: 提供替代構(gòu)造函數(shù)
有時(shí)候,你可能希望提供多種方式來(lái)創(chuàng)建類的實(shí)例。你可以利用類方法作為“工廠方法”,為不同的需求提供不同的構(gòu)造邏輯。
class Date:def __init__(self, year, month, day):self.year = yearself.month = monthself.day = day@classmethoddef from_string(cls, date_string):year, month, day = map(int, date_string.split('-'))return cls(year, month, day) # 返回一個(gè)新實(shí)例# 使用標(biāo)準(zhǔn)構(gòu)造函數(shù)
date1 = Date(2023, 4, 1)# 使用類方法提供的替代構(gòu)造函數(shù)
date2 = Date.from_string("2023-04-01")print(date1.year, date1.month, date1.day) # 輸出:2023 4 1
print(date2.year, date2.month, date2.day) # 輸出:2023 4 1
這里,from_string
類方法允許用戶從字符串格式的數(shù)據(jù)創(chuàng)建Date
對(duì)象,這增加了靈活性。
示例 3: 調(diào)用其他類方法
類方法還可以調(diào)用其他的類方法或靜態(tài)方法,這在需要鏈?zhǔn)讲僮骰蛘邚?fù)用已有邏輯的情況下非常有用。
class MathOperations:@classmethoddef add(cls, a, b):return a + b@classmethoddef multiply(cls, a, b):return a * b@classmethoddef combined_operation(cls, a, b):sum_result = cls.add(a, b)product_result = cls.multiply(a, b)return sum_result, product_resultresult = MathOperations.combined_operation(5, 3)
print(result) # 輸出:(8, 15),分別是加法和乘法的結(jié)果
在上面的例子中,combined_operation
類方法內(nèi)部調(diào)用了另外兩個(gè)類方法add
和multiply
來(lái)完成一系列計(jì)算。
3. 迭代器和生成器
(1) 迭代器(Iterator
)
迭代器是一個(gè)可以記住遍歷位置的對(duì)象,它從集合的第一個(gè)元素開(kāi)始訪問(wèn),直到所有的元素被訪問(wèn)完結(jié)束。迭代器只能往前不會(huì)后退。 在Python
中,要?jiǎng)?chuàng)建一個(gè)迭代器對(duì)象,需要實(shí)現(xiàn)兩個(gè)方法:__iter__()
和 __next__()
(1.1) 內(nèi)置迭代器:
序列迭代器: 列表、元組、字符串等序列類型都有默認(rèn)的迭代器。
my_list = [1, 2, 3]
iterator = iter(my_list)
print(next(iterator)) # 輸出: 1
字典視圖迭代器: 如 .keys()
, .values()
, .items()
返回的都是迭代器對(duì)象。
python
深色版本
my_dict = {'a': 1, 'b': 2}
keys_iterator = iter(my_dict.keys())
print(next(keys_iterator)) # 輸出: 'a'
文件迭代器: 打開(kāi)的文件對(duì)象也是迭代器,可用于逐行讀取文件內(nèi)容。
with open('example.txt', 'r') as file:for line in file:print(line.strip())
(1.2) 自定義迭代器
- iter():返回迭代器對(duì)象本身。
- next():返回容器中的下一個(gè)值。如果沒(méi)有更多的元素可供返回,則拋出 StopIteration 異常。 迭代器的一個(gè)重要特性是可以節(jié)省內(nèi)存,因?yàn)樗恍枰淮涡约虞d整個(gè)數(shù)據(jù)集到內(nèi)存中,而是按需生成數(shù)據(jù)。
class MyIterator:def __init__(self, max_value):self.max_value = max_valueself.current = 0def __iter__(self):return selfdef __next__(self):if self.current < self.max_value:value = self.currentself.current += 1return valueelse:raise StopIteration# 使用自定義迭代器
my_iter = MyIterator(3)
for i in my_iter:print(i) # 輸出: 0, 1, 2
(2) 生成器(Generator
)
(2.1) 生成器函數(shù)
生成器是一種特殊的迭代器,它是通過(guò)函數(shù)來(lái)創(chuàng)建的,但是與普通函數(shù)不同的是,生成器使用了 yield
關(guān)鍵字而不是 return
。每當(dāng)生成器函數(shù)執(zhí)行到 yield
語(yǔ)句時(shí),它會(huì)暫停并保存當(dāng)前的所有狀態(tài),然后返回 yield 的值給調(diào)用者。當(dāng)后續(xù)再次調(diào)用生成器時(shí),它會(huì)從上次離開(kāi)的地方繼續(xù)執(zhí)行 。這種機(jī)制使得生成器非常適合處理大數(shù)據(jù)集或惰性計(jì)算(lazy evaluation
),因?yàn)樗恍枰淮涡约虞d所有數(shù)據(jù)到內(nèi)存中。
yield 與 return 的區(qū)別:
return
:一 旦執(zhí)行了 return
語(yǔ)句,函數(shù)就會(huì)結(jié)束,并且所有的局部變量都會(huì)被銷毀。
yield
: 每當(dāng)執(zhí)行到 yield
語(yǔ)句時(shí),函數(shù)會(huì)暫停并返回一個(gè)值給調(diào)用者,但是函數(shù)的狀態(tài)會(huì)被保存下來(lái),下次調(diào)用時(shí)可以從上次暫停的地方繼續(xù)執(zhí)行。
def generator(n):for i in range(n):print("before yield")yield iprint("after yield")gen = generator(3)print(next(gen)) # 第一次調(diào)用next
print("---")# 使用for循環(huán)遍歷剩余的元素 自動(dòng)調(diào)用__next__
# 第二、第三次都是在這個(gè)下面調(diào)用并打印
for i in gen: print(i)
完整輸出結(jié)果:
before yield
0
---
after yield
before yield
1
after yield
before yield
2
after yield
(2.2) 生成器表達(dá)式
生成器表達(dá)式提供了一種簡(jiǎn)潔的方式來(lái)創(chuàng)建生成器,類似于列表推導(dǎo)式的語(yǔ)法,但使用圓括號(hào) ()
而不是方括號(hào) []
。與列表推導(dǎo)式不同的是,生成器表達(dá)式不會(huì)一次性生成所有元素并存儲(chǔ)在內(nèi)存中,而是按需生成每個(gè)元素。
gen_exp = (x*x for x in range(5))
print(next(gen_exp)) # 輸出: 0
print(next(gen_exp)) # 輸出: 1
print(next(gen_exp)) # 輸出: 4
# 繼續(xù)打印剩余的平方數(shù)...
4. 裝飾器
Python
裝飾器(Decorator
)是一種用于修改函數(shù)或方法行為的高級(jí)特性。它本質(zhì)上是一個(gè)返回函數(shù)的函數(shù),通常用于在不改變?cè)瘮?shù)定義的情況下,為函數(shù)添加新的功能。裝飾器廣泛應(yīng)用于日志記錄、訪問(wèn)控制、性能測(cè)量等場(chǎng)景。
4.1 基本概念
裝飾器的基本語(yǔ)法是使用 @decorator_name
語(yǔ)法語(yǔ)法糖(Syntactic Sugar
)將裝飾器應(yīng)用到一個(gè)函數(shù)或方法上。例如:
@my_decorator
def my_function():print("執(zhí)行函數(shù)")
這相當(dāng)于下面的代碼:
def my_function():print("執(zhí)行函數(shù)")
my_function = my_decorator(my_function)
簡(jiǎn)單示例
以下是一個(gè)簡(jiǎn)單的裝飾器示例,該裝飾器會(huì)在調(diào)用函數(shù)前后打印消息:
def simple_decorator(func):def wrapper():print("函數(shù)之前的操作")func()print("函數(shù)之后的操作")return wrapper@simple_decorator
def say_hello():print("Hello!")say_hello()
輸出結(jié)果將是:
函數(shù)之前的操作
Hello!
函數(shù)之后的操作
4.2 帶參數(shù)的裝飾器
如果需要裝飾的函數(shù)帶有參數(shù),可以通過(guò)在 wrapper
函數(shù)中使用 *args
和 **kwargs
來(lái)處理任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù):
def decorator_with_arguments(func):def wrapper(*args, **kwargs):print("函數(shù)之前的操作")result = func(*args, **kwargs)print("函數(shù)之后的操作")return resultreturn wrapper@decorator_with_arguments
def greet(name):print(f"Hello, {name}!")greet("Alice")
輸出結(jié)果將是:
函數(shù)之前的操作
Hello, Alice!
函數(shù)之后的操作
4.3 帶參數(shù)的裝飾器工廠
有時(shí)你可能希望裝飾器本身也接受參數(shù)。這時(shí)可以創(chuàng)建一個(gè)裝飾器工廠,即一個(gè)返回裝飾器的函數(shù):
def repeat(num_times):def decorator_repeat(func):def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator_repeat@repeat(3)
def say_hello():print("Hello!")say_hello()
這段代碼會(huì)讓 say_hello
函數(shù)執(zhí)行三次。
4.4 類裝飾器
除了函數(shù)裝飾器外,還可以使用類作為裝飾器。為此,你需要實(shí)現(xiàn) __call__()
方法,使得類實(shí)例可調(diào)用:
class ClassDecorator:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("函數(shù)之前的操作")result = self.func(*args, **kwargs)print("函數(shù)之后的操作")return result@ClassDecorator
def say_goodbye():print("Goodbye!")say_goodbye()
5. 深拷貝和淺拷貝
5.1 淺拷貝(Shallow Copy
)
淺拷貝創(chuàng)建一個(gè)新的對(duì)象,但不遞歸地復(fù)制嵌套的對(duì)象。換句話說(shuō),原對(duì)象和新對(duì)象共享嵌套對(duì)象的引用。
特點(diǎn)
- 創(chuàng)建一個(gè)新對(duì)象。
- 新對(duì)象包含對(duì)原始對(duì)象中元素的引用,而不是這些元素的副本。
- 如果原始對(duì)象中的元素是可變對(duì)象(如列表、字典),則新舊對(duì)象共享這些可變對(duì)象。
import copyoriginal_list = [[1, 2], [3, 4]]
shallow_copied_list = copy.copy(original_list)# 修改淺拷貝中的一個(gè)子列表
shallow_copied_list[0][0] = 'X'print("Original List:", original_list) # 輸出: Original List: [['X', 2], [3, 4]]
print("Shallow Copied List:", shallow_copied_list) # 輸出: Shallow Copied List: [['X', 2], [3, 4]]
可以看到,修改淺拷貝中的子列表也影響了原始列表,因?yàn)?strong>它們共享相同的子列表對(duì)象。
5.2 深拷貝(Deep Copy
)
深拷貝不僅創(chuàng)建一個(gè)新的對(duì)象,還會(huì)遞歸地復(fù)制所有嵌套的對(duì)象。這意味著新對(duì)象和原始對(duì)象完全獨(dú)立,沒(méi)有任何共享的引用。
特點(diǎn)
- 創(chuàng)建一個(gè)新對(duì)象。
- 新對(duì)象包含原始對(duì)象中所有元素的副本,包括嵌套對(duì)象的所有層級(jí)。
- 原始對(duì)象和新對(duì)象之間沒(méi)有共享的引用。
import copyoriginal_list = [[1, 2], [3, 4]]
deep_copied_list = copy.deepcopy(original_list)# 修改深拷貝中的一個(gè)子列表
deep_copied_list[0][0] = 'X'print("Original List:", original_list) # 輸出: Original List: [[1, 2], [3, 4]]
print("Deep Copied List:", deep_copied_list) # 輸出: Deep Copied List: [['X', 2], [3, 4]]
6 lambda
函數(shù)
lambda
函數(shù)的基本語(yǔ)法如下:
lambda 參數(shù)1, 參數(shù)2, ... : 表達(dá)式
- 參數(shù):可以有多個(gè)參數(shù),用逗號(hào)分隔。
- 表達(dá)式: 是一個(gè)單一的表達(dá)式,而不是一個(gè)代碼塊。
lambda
函數(shù)會(huì)返回該表達(dá)式的值。 - 快速理解
lambda
函數(shù)的一個(gè)有效方法是明確其輸入(參數(shù))和輸出(表達(dá)式的結(jié)果)。你可以將 lambda 函數(shù)視為一個(gè)簡(jiǎn)單的函數(shù)定義,并且只關(guān)注它的輸入和輸出。
示例
6.1 無(wú)參數(shù)的 lambda
函數(shù):
f = lambda: "Hello, World!"
print(f()) # 輸出: Hello, World!
6.2 帶參數(shù)的 lambda
函數(shù):
add = lambda x, y: x + y
print(add(5, 3)) # 輸出: 8
6.3 帶有默認(rèn)參數(shù)的 lambda
函數(shù):
power = lambda x, n=2: x ** n
print(power(2)) # 輸出: 4 (2^2)
print(power(2, 3)) # 輸出: 8 (2^3)
6.4 使用條件表達(dá)式的 lambda
函數(shù):
max_value = lambda a, b: a if a > b else b
print(max_value(10, 20)) # 輸出: 20
使用場(chǎng)景
lambda
函數(shù)最常用于需要將一個(gè)小函數(shù)作為參數(shù)傳遞給其他函數(shù)的場(chǎng)合,比如高階函數(shù)(如 map(), filter(), sorted()
等)。
1. map()
函數(shù)
map()
函數(shù)可以對(duì)可迭代對(duì)象中的每個(gè)元素應(yīng)用一個(gè)函數(shù),并返回一個(gè)新的迭代器。
numbers = [1, 2, 3, 4]
squared_numbers = map(lambda x: x ** 2, numbers)
print(list(squared_numbers)) # 輸出: [1, 4, 9, 16]
2. filter()
函數(shù)
filter()
函數(shù)可以根據(jù)指定條件過(guò)濾可迭代對(duì)象中的元素。
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers)) # 輸出: [2, 4, 6]
3. sorted()
函數(shù)
sorted()
函數(shù)可以根據(jù)指定的關(guān)鍵字對(duì)可迭代對(duì)象進(jìn)行排序。
students = [{"name": "Alice", "age": 25},{"name": "Bob", "age": 20},{"name": "Charlie", "age": 22}
]sorted_students = sorted(students, key=lambda student: student["age"])
print(sorted_students)
# 輸出: [{'name': 'Bob', 'age': 20}, {'name': 'Charlie', 'age': 22}, {'name': 'Alice', 'age': 25}]
4. 在列表推導(dǎo)式中使用 lambda
雖然列表推導(dǎo)式本身已經(jīng)很簡(jiǎn)潔了,但在某些情況下結(jié)合 lambda
函數(shù)可以進(jìn)一步簡(jiǎn)化代碼。
numbers = [1, 2, 3, 4]
doubled = [(lambda x: x * 2)(n) for n in numbers]
print(doubled) # 輸出: [2, 4, 6, 8]
7. Python垃圾回收機(jī)制
Python
的垃圾回收機(jī)制(Garbage Collection, GC
)主要用于自動(dòng)管理內(nèi)存,釋放不再使用的對(duì)象所占用的內(nèi)存資源。理解 Python
的垃圾回收機(jī)制有助于編寫(xiě)更高效的代碼,并避免內(nèi)存泄漏等問(wèn)題。
7.1 引用計(jì)數(shù)(Reference Counting
)
這是 Python 最基本的垃圾回收機(jī)制。每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)器,記錄當(dāng)前有多少個(gè)引用指向該對(duì)象。當(dāng)引用計(jì)數(shù)變?yōu)榱銜r(shí),說(shuō)明沒(méi)有其他對(duì)象在使用它,可以安全地釋放其占用的內(nèi)存。
工作原理
- 當(dāng)一個(gè)對(duì)象被創(chuàng)建并賦值給一個(gè)變量時(shí),該對(duì)象的引用計(jì)數(shù)加一。
- 當(dāng)有新的變量引用同一個(gè)對(duì)象時(shí),引用計(jì)數(shù)再次加一。
- 當(dāng)某個(gè)變量不再引用該對(duì)象時(shí),引用計(jì)數(shù)減一。
- 當(dāng)引用計(jì)數(shù)降為零時(shí),
Python
自動(dòng)調(diào)用該對(duì)象的__del__
方法(如果定義了),然后釋放其占用的內(nèi)存。
7.2 循環(huán)引用檢測(cè)(Cycle Detection
)
雖然引用計(jì)數(shù)機(jī)制簡(jiǎn)單高效,但它無(wú)法處理循環(huán)引用的情況。例如,兩個(gè)對(duì)象相互引用形成一個(gè)閉環(huán),即使這些對(duì)象已經(jīng)沒(méi)有任何外部引用,它們的引用計(jì)數(shù)也不會(huì)降為零,從而導(dǎo)致內(nèi)存泄漏。
為了解決這個(gè)問(wèn)題,Python
使用了一個(gè)基于 標(biāo)記-清除(Mark-and-Sweep
) 和 分代收集(Generational Garbage Collection
) 的算法來(lái)檢測(cè)和清理循環(huán)引用。
工作原理
標(biāo)記-清除算法分為兩個(gè)主要階段:標(biāo)記階段 和 清除階段。
標(biāo)記階段:
- 從根對(duì)象(如全局變量、活動(dòng)棧幀等)開(kāi)始遍歷所有可達(dá)對(duì)象,并將這些對(duì)象標(biāo)記為活躍對(duì)象。
- 根對(duì)象是指那些始終可以被訪問(wèn)的對(duì)象,例如當(dāng)前正在使用的變量、函數(shù)調(diào)用棧中的局部變量等。
清除階段:
- 掃描整個(gè)堆內(nèi)存,找到未被標(biāo)記的對(duì)象并將其刪除。
- 清除階段會(huì)釋放這些對(duì)象占用的內(nèi)存,并將其返回給可用內(nèi)存池。
假設(shè)我們有以下對(duì)象圖:
A -> B
B -> C
C -> D
D -> E
E -> A (循環(huán)引用)
在這個(gè)例子中,A -> B -> C -> D -> E -> A
形成了一個(gè)循環(huán)引用。如果沒(méi)有其他外部引用指向這些對(duì)象,那么即使引用計(jì)數(shù)不會(huì)降為零,這些對(duì)象也應(yīng)該被回收。
標(biāo)記階段:
- 從根對(duì)象開(kāi)始遍歷,假設(shè)只有
A
被根對(duì)象引用。 - 遍歷
A
,發(fā)現(xiàn)它引用了B
,標(biāo)記B
。 - 遍歷
B
,發(fā)現(xiàn)它引用了C
,標(biāo)記C
。 繼續(xù)遍歷 C
和D
,標(biāo)記D
和E
。- 最終,所有對(duì)象都被標(biāo)記為活躍對(duì)象。
清除階段:
- 掃描整個(gè)堆內(nèi)存,發(fā)現(xiàn)沒(méi)有未被標(biāo)記的對(duì)象,因此不需要清除任何對(duì)象。
如果根對(duì)象不再引用 A
,則在標(biāo)記階段無(wú)法到達(dá) A
及其循環(huán)引用鏈上的對(duì)象,這些對(duì)象會(huì)被標(biāo)記為不可達(dá)并在清除階段被刪除。
分代回收(Generational Garbage Collection
)
分代回收是一種優(yōu)化策略,基于一個(gè)觀察:大多數(shù)對(duì)象在創(chuàng)建后很快就會(huì)被銷毀,而那些存活較長(zhǎng)時(shí)間的對(duì)象不太可能被銷毀。因此,Python
將對(duì)象分為三個(gè)世代(Generation
),分別是第 0 代、第 1 代和第 2 代。
工作原理
-
(1) 對(duì)象分配與提升:
- 新創(chuàng)建的對(duì)象屬于第
0
代。 - 當(dāng)?shù)?
0
代對(duì)象經(jīng)過(guò)一次垃圾回收后仍然存活,它會(huì)被提升到第1
代。 - 類似地,第
1
代對(duì)象經(jīng)過(guò)垃圾回收后仍然存活,則會(huì)被提升到第2
代。
- 新創(chuàng)建的對(duì)象屬于第
-
(2) 垃圾回收頻率:
- 第
0
代對(duì)象的垃圾回收頻率最高,因?yàn)樗鼈冏钣锌赡鼙豢焖黉N毀。 - 第
1
代和第2
代對(duì)象的垃圾回收頻率逐漸降低,因?yàn)樗鼈兏锌赡荛L(zhǎng)期存活。
- 第
-
(3) 閾值設(shè)置:
- 每個(gè)世代都有一個(gè)閾值,表示在該世代對(duì)象數(shù)量達(dá)到一定值時(shí)觸發(fā)垃圾回收。
- 默認(rèn)的閾值可以通過(guò)
gc.get_threshold()
獲取,并且可以通過(guò)gc.set_threshold(gen0, gen1, gen2)
進(jìn)行設(shè)置。
8 多線程與多進(jìn)程
在 Python
中,多線程(Multithreading
)和多進(jìn)程(Multiprocessing
)是兩種常見(jiàn)的并行編程方法,用于提高程序的性能和響應(yīng)速度。盡管它們都旨在實(shí)現(xiàn)并發(fā)執(zhí)行,但它們的工作原理和適用場(chǎng)景有所不同。
CPU
密集型任務(wù): CPU
密集型任務(wù)是指那些主要依賴于 CPU
計(jì)算能力的任務(wù)。這類任務(wù)通常需要大量的計(jì)算資源,包括復(fù)雜的數(shù)學(xué)運(yùn)算、數(shù)據(jù)處理、圖像處理、視頻編碼等。
I/O
密集型任務(wù): I/O
密集型任務(wù)是指那些主要依賴于外部輸入輸出操作的任務(wù)。這類任務(wù)通常涉及大量的文件讀寫(xiě)、網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)查詢等。
8.1 多進(jìn)程
基本概念
多進(jìn)程是指在一個(gè)程序中同時(shí)運(yùn)行多個(gè)進(jìn)程。每個(gè)進(jìn)程都有獨(dú)立的內(nèi)存空間和解釋器實(shí)例, 因此它們可以真正并行執(zhí)行任務(wù),不受 GIL 的限制。
工作原理
8.1.1進(jìn)程間通信(IPC
):
- 進(jìn)程之間不能直接共享內(nèi)存,必須通過(guò)特定的機(jī)制進(jìn)行通信,如
Queue
、Pipe
或Manager
對(duì)象。 multiprocessing.Queue
提供了一個(gè)線程和進(jìn)程安全的隊(duì)列,用于在不同進(jìn)程之間傳遞數(shù)據(jù)。multiprocessing.Pipe
提供了雙向通信通道,適用于父子進(jìn)程之間的通信。
8.1.2 創(chuàng)建和管理進(jìn)程:
- 使用
multiprocessing.Process
類來(lái)創(chuàng)建和管理進(jìn)程。 - 進(jìn)程可以通過(guò)調(diào)用
start()
方法開(kāi)始執(zhí)行,并通過(guò)join()
方法等待進(jìn)程完成。
示例代碼
python
深色版本
import multiprocessing
import timedef print_numbers(queue):for i in range(5):queue.put(f"數(shù)字: {i}")time.sleep(1)def print_letters(queue):for letter in 'ABCDE':queue.put(f"字母: {letter}")time.sleep(1)if __name__ == "__main__":# 創(chuàng)建一個(gè)隊(duì)列用于進(jìn)程間通信queue = multiprocessing.Queue()# 創(chuàng)建進(jìn)程p1 = multiprocessing.Process(target=print_numbers, args=(queue,))p2 = multiprocessing.Process(target=print_letters, args=(queue,))# 啟動(dòng)進(jìn)程p1.start()p2.start()# 從隊(duì)列中讀取數(shù)據(jù)并打印while True:if not queue.empty():print(queue.get())if not p1.is_alive() and not p2.is_alive():break# 等待兩個(gè)進(jìn)程完成p1.join()p2.join()
8.2 多線程(Multithreading
)
基本概念
多線程是指在一個(gè)進(jìn)程中同時(shí)運(yùn)行多個(gè)線程。每個(gè)線程都是一個(gè)獨(dú)立的執(zhí)行路徑,可以并發(fā)執(zhí)行不同的任務(wù)。Python
的 threading
模塊提供了對(duì)多線程的支持。
工作原理
(1)GIL(Global Interpreter Lock)
:
CPython
解釋器使用GIL
來(lái)確保同一時(shí)刻只有一個(gè)線程在執(zhí)行Python
字節(jié)碼(Python
源代碼被編譯成字節(jié)碼,這是一種低級(jí)的中間表示形式,由Python
虛擬機(jī)解釋執(zhí)行)。這意味著即使有多個(gè)線程,它們也不能真正并行執(zhí)行CPU
密集型任務(wù)。- 但是,對(duì)于
I/O
密集型任務(wù)(如文件讀寫(xiě)、網(wǎng)絡(luò)請(qǐng)求等),多線程仍然可以提高效率,因?yàn)檫@些任務(wù)在等待I/O
操作時(shí)會(huì)釋放GIL
,允許其他線程繼續(xù)執(zhí)行。
(2)創(chuàng)建和管理線程:
- 使用
threading.Thread
類來(lái)創(chuàng)建和管理線程。 - 線程可以通過(guò)調(diào)用
start()
方法開(kāi)始執(zhí)行,并通過(guò)join()
方法等待線程完成。
知識(shí)擴(kuò)展
GIL 的作用
- 簡(jiǎn)化內(nèi)存管理: GIL 簡(jiǎn)化了 Python 內(nèi)存管理的設(shè)計(jì),使得解釋器不需要處理復(fù)雜的線程同步問(wèn)題。
- 保護(hù)內(nèi)置數(shù)據(jù)結(jié)構(gòu): 許多 Python 內(nèi)置的數(shù)據(jù)結(jié)構(gòu)和庫(kù)并不是線程安全的,GIL 提供了一種簡(jiǎn)單的保護(hù)機(jī)制,防止多個(gè)線程同時(shí)修改這些數(shù)據(jù)結(jié)構(gòu)。
為什么說(shuō) Python 的多線程是“假的”
- 無(wú)法實(shí)現(xiàn)真正的并行計(jì)算:由于 GIL 的存在,多線程不能真正并行執(zhí)行 CPU 密集型任務(wù)。即使在多核 CPU 上,也只能有一個(gè)線程在執(zhí)行Python 字節(jié)碼,這與我們通常理解的多線程并行計(jì)算相悖。
- 增加了上下文切換的開(kāi)銷:在某些情況下,特別是當(dāng)線程頻繁切換時(shí),上下文切換的開(kāi)銷可能會(huì)導(dǎo)致性能下降,甚至比單線程執(zhí)行還要慢。
- 誤導(dǎo)性:初學(xué)者可能會(huì)誤以為使用多線程可以顯著提升程序的性能,尤其是在 CPU 密集型任務(wù)中,但實(shí)際上效果并不明顯,甚至可能適得其反。
示例代碼
import threading
import timedef print_numbers():for i in range(5):print(f"數(shù)字: {i}")time.sleep(1)def print_letters():for letter in 'ABCDE':print(f"字母: {letter}")time.sleep(1)# 創(chuàng)建線程
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)# 啟動(dòng)線程
t1.start()
t2.start()# 等待兩個(gè)線程完成
t1.join()
t2.join()