欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python內(nèi)存優(yōu)化的七種技巧分享

 更新時間:2024年02月03日 08:58:59   作者:我下班楽蟹老板  
本文將重點介紹 Python 的內(nèi)置機制,并介紹 7 種原始但有效的內(nèi)存優(yōu)化技巧,掌握這些技巧將大大提高你的 Python 編程能力,文中通過代碼示例給大家講解的非常詳細(xì),感興趣的小伙伴跟著小編一起來學(xué)習(xí)吧

當(dāng)您的項目規(guī)模越來越大時,高效管理內(nèi)存資源就成為必然要求。遺憾的是,Python,尤其是與 C 或 C++ 等低級語言相比,似乎內(nèi)存效率不夠高?,F(xiàn)在是否應(yīng)該更換編程語言?當(dāng)然不是。事實上,從優(yōu)秀的模塊和工具到先進的數(shù)據(jù)結(jié)構(gòu)和算法,有很多方法可以顯著優(yōu)化 Python 程序的內(nèi)存使用。

本文將重點介紹 Python 的內(nèi)置機制,并介紹 7 種原始但有效的內(nèi)存優(yōu)化技巧。掌握這些技巧將大大提高你的 Python 編程能力。

1. 在類定義中使用 __slots__

Python 作為一種動態(tài)類型編程語言,在 OOP 方面有更大的靈活性。在運行時向 Python 類中添加額外的屬性和方法就是一個很好的例子。

例如,下面的代碼定義了一個名為 Author 的類。它最初有兩個屬性 name 和 age。但我們可以在稍后輕松地添加一個額外的屬性:

class Author:
    def __init__(self, name, age):
        self.name = name
        self.age = age


me = Author('Yang Zhou', 30)
me.job = 'Software Engineer'
print(me.job)
# Software Engineer

然而,任何硬幣都有兩面。這種靈活性會浪費更多內(nèi)存。

因為 Python 中每個類的實例都會維護一個特殊的字典 (__dict__),用于存儲實例變量。由于字典的底層是基于哈希表的實現(xiàn),因此字典本身的內(nèi)存效率很低,因此字典會消耗大量內(nèi)存。

在大多數(shù)情況下,我們不需要在運行時更改實例的變量或方法,而且在類定義之后,__dict__ 也不會被更改。因此,我們最好不要維護 __dict__ 字典。

Python 為此提供了一個神奇的屬性:__slots__

它通過指定類的所有有效屬性的名稱,起到白名單的作用:

class Author:
    __slots__ = ('name', 'age')

    def __init__(self, name, age):
        self.name = name
        self.age = age


me = Author('Yang Zhou', 30)
me.job = 'Software Engineer'
print(me.job)
# AttributeError: 'Author' object has no attribute 'job'

就像上面的代碼,我們不能再在運行時添加工作屬性了。因為 __slots__ 白名單只定義了 nameage 兩個有效屬性。

從理論上講,由于屬性是固定的,Python 不需要為它維護一個字典。它只需為 __slots__ 中定義的屬性分配必要的內(nèi)存空間即可。

讓我們寫一個簡單的比較程序來看看它是否真的可以這樣工作:

import sys


class Author:
    def __init__(self, name, age):
        self.name = name
        self.age = age


class AuthorWithSlots:
    __slots__ = ['name', 'age']

    def __init__(self, name, age):
        self.name = name
        self.age = age


# Creating instances
me = Author('Yang', 30)
me_with_slots = AuthorWithSlots('Yang', 30)

# Comparing memory usage
memory_without_slots = sys.getsizeof(me) + sys.getsizeof(me.__dict__)
memory_with_slots = sys.getsizeof(me_with_slots)  # __slots__ classes don't have __dict__

print(memory_without_slots, memory_with_slots)
# 152 48
print(me.__dict__)
# {'name': 'Yang', 'age': 30}
print(me_with_slots.__dict__)
# AttributeError: 'AuthorWithSlots' object has no attribute '__dict__'

如上面的代碼所示,由于使用了 __slots__,實例 me_with_slots 沒有__dict__ 字典。與需要保存額外字典的 me 實例相比,它有效地節(jié)省了內(nèi)存資源。

2. 使用生成器

生成器是 Python 中列表的懶版本。它們的工作方式類似于元素生成工廠:每當(dāng)調(diào)用 next() 方法時就生成一個項,而不是一次性計算所有項。因此,在處理大型數(shù)據(jù)集時,它們非常節(jié)省內(nèi)存。

def number_generator():
    for i in range(100):
        yield i

numbers = number_generator()
print(numbers)
# <generator object number_generator at 0x104a57e40>
print(next(numbers))
# 0
print(next(numbers))
# 1

上面的代碼展示了一個編寫和使用生成器的基本示例。關(guān)鍵字 yield 是生成器定義的核心。使用它意味著只有在調(diào)用 next() 方法時,才會產(chǎn)生項目 i

現(xiàn)在,讓我們比較一下生成器和列表,看看哪一個更節(jié)省內(nèi)存:

import sys

numbers = []
for i in range(100):
    numbers.append(i)

def number_generator():
    for i in range(100):
        yield i

numbers_generator = number_generator()
print(sys.getsizeof(numbers_generator))
# 112
print(sys.getsizeof(numbers))
# 920

上述程序的結(jié)果證明,使用生成器可以大大節(jié)省內(nèi)存使用量。

順便提一下,如果我們把 list 理解的方括號轉(zhuǎn)換成小括號,它就會變成一個生成器表達式。這是在 Python 中定義生成器的一種更簡單的方法:

import sys

numbers = [i for i in range(100)]
numbers_generator = (i for i in range(100))

print(sys.getsizeof(numbers_generator))
# 112
print(sys.getsizeof(numbers))
# 920

3. 利用內(nèi)存映射文件支持大文件處理

內(nèi)存映射文件 I/O,簡稱 mmap,是一種操作系統(tǒng)級優(yōu)化。

維基百科:它實現(xiàn)了需求分頁,因為文件內(nèi)容不會立即從磁盤讀取,最初根本不使用物理 RAM。從磁盤實際讀取的操作是在訪問特定位置后,以一種懶惰的方式進行的。

簡單地說,當(dāng)使用 mmap 技術(shù)對文件進行內(nèi)存映射時,它會直接在當(dāng)前進程的虛擬內(nèi)存空間中創(chuàng)建文件的映射,而不是將整個文件加載到內(nèi)存中。映射而不是加載整個文件可以節(jié)省大量內(nèi)存。

看起來很復(fù)雜?幸運的是,Python 已經(jīng)提供了使用這種技術(shù)的內(nèi)置模塊,因此我們可以輕松利用它,而無需考慮操作系統(tǒng)級的實現(xiàn)。

例如,在 Python 中如何使用 mmap 進行文件處理:

import mmap


with open('test.txt', "r+b") as f:
    # memory-map the file, size 0 means whole file
    with mmap.mmap(f.fileno(), 0) as mm:
        # read content via standard file methods
        print(mm.read())
        # read content via slice notation
        snippet = mm[0:10]
        print(snippet.decode('utf-8'))

如上所述,Python 使內(nèi)存映射文件 I/O 技術(shù)的使用變得非常方便。我們需要做的僅僅是應(yīng)用 mmap.mmap() 方法,然后使用標(biāo)準(zhǔn)文件方法甚至切片符號來處理打開的對象。

4. 盡量少用全局變量

全局變量具有全局作用域,因此只要程序運行,全局變量就會一直保留在內(nèi)存中。

因此,如果一個全局變量包含一個大型數(shù)據(jù)結(jié)構(gòu),它就會在整個程序生命周期中占用內(nèi)存,從而可能導(dǎo)致內(nèi)存使用效率低下。

我們應(yīng)該在 Python 代碼中盡量減少全局變量的使用。

5. 利用邏輯操作符

這個技巧看似微妙,但巧妙地使用它將極大地節(jié)省程序的內(nèi)存使用量。

例如,下面是一個簡單的代碼片段,它根據(jù)兩個函數(shù)返回的布爾值得到最終結(jié)果:

result_a = expensive_function_a()
result_b = expensive_function_b()
result = result_a if result_a else result_b

上述代碼可以正常運行,但它實際上執(zhí)行了兩個內(nèi)存不足的函數(shù)。

獲得相同結(jié)果的更聰明的方法如下:

result = expensive_function1() or expensive_function2()

由于邏輯運算符遵循短路評估規(guī)則,如果 expensive_function1()True,則不會執(zhí)行上述代碼中的 expensive_function2()。這將節(jié)省不必要的內(nèi)存使用。

6. 謹(jǐn)慎選擇數(shù)據(jù)類型

資深的 Python 開發(fā)人員會謹(jǐn)慎而精確地選擇數(shù)據(jù)類型。因為在某些情況下,使用一種數(shù)據(jù)類型比使用另一種數(shù)據(jù)類型更節(jié)省內(nèi)存。

元組比列表更節(jié)省內(nèi)存

鑒于元組是不可變的(創(chuàng)建后不能更改),它允許 Python 在內(nèi)存分配方面進行優(yōu)化。然而,列表是可變的,因此需要額外的空間來容納潛在的修改。

import sys

my_tuple = (1, 2, 3, 4, 5)
my_list = [1, 2, 3, 4, 5]

print(sys.getsizeof(my_tuple))
# 80
print(sys.getsizeof(my_list)) 
# 120

如上面的代碼段所示,即使包含相同的元素,元組 my_tuple 使用的內(nèi)存也比 list 少。

因此,如果在創(chuàng)建后不需要更改數(shù)據(jù),我們應(yīng)該首選元組而不是列表。

數(shù)組比 list 更節(jié)省內(nèi)存

Python 中的數(shù)組要求元素具有相同的數(shù)據(jù)類型(例如,所有整數(shù)或所有浮點數(shù)),但列表可以存儲不同類型的對象,這就不可避免地需要更多內(nèi)存。

因此,如果列表的元素都是同一類型,使用數(shù)組會更節(jié)省內(nèi)存:

import sys
import array

my_list = [i for i in range(1000)]

my_array = array.array('i', [i for i in range(1000)])

print(sys.getsizeof(my_list))  
# 8856
print(sys.getsizeof(my_array)) 
# 4064

優(yōu)秀的數(shù)據(jù)科學(xué)模塊比內(nèi)置數(shù)據(jù)類型更高效

Python 是數(shù)據(jù)科學(xué)的統(tǒng)治語言。有許多強大的第三方模塊和工具提供了更多的數(shù)據(jù)類型,如 NumPy 和 Pandas。

如果我們只需要一個簡單的一維數(shù)組,而不需要 NumPy 提供的廣泛功能,那么 Python 的內(nèi)置數(shù)組可能是一個不錯的選擇。

但如果需要進行復(fù)雜的矩陣操作,使用 NumPy 提供的數(shù)組可能是所有數(shù)據(jù)科學(xué)家的首選,也可能是最佳選擇。

7. 對相同字符串應(yīng)用字符串互文技術(shù)

下面的代碼會讓很多開發(fā)人員感到困惑:

>>> a = 'Y'*4096
>>> b = 'Y'*4096
>>> a is b
True

>>> c = 'Y'*4097
>>> d = 'Y'*4097
>>> c is d
False

我們知道,is 運算符用于檢查兩個變量是否指向內(nèi)存中的同一個對象。它與 == 運算符不同,后者用于比較兩個對象是否具有相同的值。

那么,為什么 a is b 得到的是 True,而 c is d 得到的卻是 False 呢?

如果有幾個小字符串的值相同,Python 就會隱式地對它們進行內(nèi)聯(lián),并引用內(nèi)存中的同一個對象。

定義小字符串的神奇數(shù)字是 4096。因為 c 和 d 的長度都是 4097,所以它們在內(nèi)存中是兩個對象,而不是一個。不再有隱式字符串互調(diào)。因此,當(dāng)執(zhí)行 c 是 d 時,我們會得到一個 False。

字符串互調(diào)是一種優(yōu)化內(nèi)存使用的強大技術(shù)。如果我們想顯式地進行字符串互調(diào),sys.intern() 方法就很好用:

>>> import sys
>>> c = sys.intern('Y'*4097)
>>> d = sys.intern('Y'*4097)
>>> c is d
True

順便說一下,除了字符串互調(diào),Python 還將互調(diào)技巧應(yīng)用于小整數(shù)。我們還可以利用它來優(yōu)化內(nèi)存。

以上就是Python內(nèi)存優(yōu)化的七種技巧分享的詳細(xì)內(nèi)容,更多關(guān)于Python內(nèi)存優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論