程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python進階系列(七)

編輯:Python

目錄

對象變動(Mutation)

__slots__魔法


對象變動(Mutation)

Python中可變(mutable)與不可變(immutable)的數據類型讓新手很是頭痛。簡單的說,可變(mutable)意味著"可以被改動",而不可變(immutable)的意思是“常量(constant)”。想把腦筋轉動起來嗎?考慮下這個例子:

foo = ['hi']
print(foo)
# Output: ['hi']
bar = foo
bar += ['bye']
print(foo)
# Output: ['hi', 'bye']

剛剛發生了什麼?我們預期的不是那樣!我們期望看到是這樣的:

foo = ['hi']
print(foo)
# Output: ['hi']
bar = foo
bar += ['bye']
print(foo)
# Output: ['hi']
print(bar)
# Output: ['hi', 'bye']

這不是一個bug。這是對象可變性(mutability)在作怪。每當你將一個變量賦值為另一個可變類型的變量時,對這個數據的任意改動會同時反映到這兩個變量上去。新變量只不過是老變量的一個別名而已。這個情況只是針對可變數據類型。下面的函數和可變數據類型讓你一下就明白了:

def add_to(num, target=[]):
    target.append(num)
    return target
add_to(1)
# Output: [1]
add_to(2)
# Output: [1, 2]
add_to(3)
# Output: [1, 2, 3]

你可能預期它表現的不是這樣子。你可能希望,當你調用add_to時,有一個新的列表被創建,就像這樣:

def add_to(num, target=[]):
    target.append(num)
    return target
add_to(1)
# Output: [1]
add_to(2)
# Output: [2]
add_to(3)
# Output: [3]

啊哈!這次又沒有達到預期,是列表的可變性在作怪。在Python中當函數被定義時,默認參數只會運算一次,而不是每次被調用時都會重新運算。你應該永遠不要定義可變類型的默認參數,除非你知道你正在做什麼。你應該像這樣做:

def add_to(element, target=None):
    if target is None:
        target = []
    target.append(element)
    return target

現在每當你在調用這個函數不傳入target參數的時候,一個新的列表會被創建。舉個例子:

add_to(42)
# Output: [42]
add_to(42)
# Output: [42]
add_to(42)
# Output: [42]

__slots__魔法

在Python中,每個類都有實例屬性。默認情況下Python用一個字典來保存一個對象的實例屬性。這非常有用。因為它允許我們在運行時去設置任意的新屬性。

然而,對於有著已知屬性的小類來說,它可能是個瓶頸。這個字典浪費了很多內存。

Python不能在對象創建時直接分配一個固定量的內存來保存所有的屬性。因此如果你創建許多對象(我指的是成千上萬個),它會消耗掉很多內存。

不過還是有一個方法來規避這個問題。這個方法需要使用__slots__來告訴Python不要使用字典,而且只給一個固定集合的屬性分配空間。

這裡是一個使用與不使用__slots__的例子:

不使用__slots__:

class MyClass(object):
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()

使用__slots__:

class MyClass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()

第二段代碼會為你的內存減輕負擔。通過這個技巧,有些人已經看到內存占用率幾乎40%~50%的減少。

*稍微備注一下,你也許需要試一下PyPy。它已經默認地做了所有這些優化。

以下你可以看到一個例子,它用IPython來展示在有與沒有__slots__情況下的精確內存

占用。

Python 3.4.3 (default, Jun 6 2015, 13:32:34)

Type "copyright", "credits" or "license" for more information.

IPython 4.0.0 -- An enhanced Interactive Python.

?           -> Introduction and overview of IPython's features.

%quickref   -> Quick reference.

help        -> Python's own help system.

object?     -> Details about 'object', use 'object??' for extra details.

In [1]: import ipython_memory_usage.ipython_memory_usage as imu

In [2]: imu.start_watching_memory()

In [2] used 0.0000 MiB RAM in 5.31s, peaked 0.00 MiB above current, total RAM

In [3]: %cat slots.py

class MyClass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier

num = 1024*256

x = [MyClass(1,1) for i in range(num)]

In [3] used 0.2305 MiB RAM in 0.12s, peaked 0.00 MiB above current, total RAM

In [4]: from slots import *

In [4] used 9.3008 MiB RAM in 0.72s, peaked 0.00 MiB above current, total RAM

In [5]: %cat noslots.py

class MyClass(object):
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier

num = 1024*256

x = [MyClass(1,1) for i in range(num)]

In [5] used 0.1758 MiB RAM in 0.12s, peaked 0.00 MiB above current, total RAM

In [6]: from noslots import *

In [6] used 22.6680 MiB RAM in 0.80s, peaked 0.00 MiB above current


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved