簡體   English   中英

不可變與可變類型

[英]Immutable vs Mutable types

我對什么是不可變類型感到困惑。 我知道float對象被認為是不可變的,我的書中有這種類型的例子:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))

由於類結構/層次結構,這是否被認為是不可變的?這意味着float位於類的頂部並且是它自己的方法調用。 類似於這種類型的示例(即使我的書說dict是可變的):

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

而可變的東西在類中有方法,用這種類型的例子:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

另外,對於最后一個class(SortedKeyDict_a) ,如果我將這種類型的集合傳遞給它:

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

不調用example方法,它返回一個字典。 帶有__new__SortedKeyDict__new__標記為錯誤。 我嘗試使用__new__將整數傳遞給RoundFloat類,並且沒有標記錯誤。

什么? 浮點數是不可變的嗎? 但我做不到

x = 5.0
x += 7.0
print x # 12.0

那不是“mut”x嗎?

好吧,您同意字符串是不可變的,對嗎? 但是你可以做同樣的事情。

s = 'foo'
s += 'bar'
print s # foobar

變量的值會發生變化,但它會通過更改變量所指的內容而發生變化。 可變類型可以以這種方式改變,它也可以“就地”改變。

這是區別。

x = something # immutable type
print x
func(x)
print x # prints the same thing

x = something # mutable type
print x
func(x)
print x # might print something different

x = something # immutable type
y = x
print x
# some statement that operates on y
print x # prints the same thing

x = something # mutable type
y = x
print x
# some statement that operates on y
print x # might print something different

具體例子

x = 'foo'
y = x
print x # foo
y += 'bar'
print x # foo

x = [1, 2, 3]
y = x
print x # [1, 2, 3]
y += [3, 2, 1]
print x # [1, 2, 3, 3, 2, 1]

def func(val):
    val += 'bar'

x = 'foo'
print x # foo
func(x)
print x # foo

def func(val):
    val += [3, 2, 1]

x = [1, 2, 3]
print x # [1, 2, 3]
func(x)
print x # [1, 2, 3, 3, 2, 1]

您必須了解 Python 將其所有數據表示為對象。 其中一些對象(如列表和字典)是可變的,這意味着您可以更改其內容而無需更改其身份。 其他對象如整數、浮點數、字符串和元組是無法更改的對象。 理解這一點的一種簡單方法是查看對象 ID。

下面你會看到一個不可變的字符串。 你不能改變它的內容。 如果您嘗試更改它,它將引發TypeError 此外,如果我們分配新內容,則會創建一個新對象,而不是修改內容。

>>> s = "abc"
>>> id(s)
4702124
>>> s[0] 
'a'
>>> s[0] = "o"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>> id(s)
4800100
>>> s += "uvw"
>>> id(s)
4800500

你可以用一個列表來做到這一點,它不會改變對象的身份

>>> i = [1,2,3]
>>> id(i)
2146718700
>>> i[0] 
1
>>> i[0] = 7
>>> id(i)
2146718700

要閱讀有關 Python 數據模型的更多信息,您可以查看 Python 語言參考:

常見的不可變類型:

  1. 數字: int()float()complex()
  2. 不可變序列: str()tuple()frozenset()bytes()

常見的可變類型(幾乎所有其他類型):

  1. 可變序列: list() , bytearray()
  2. 設置類型: set()
  3. 映射類型: dict()
  4. 類,類實例
  5. 等等。

快速測試類型是否可變的一個技巧是使用id()內置函數。

示例,使用整數,

>>> i = 1
>>> id(i)
***704
>>> i += 1
>>> i
2
>>> id(i)
***736 (different from ***704)

使用清單,

>>> a = [1]
>>> id(a)
***416
>>> a.append(2)
>>> a
[1, 2]
>>> id(a)
***416 (same with the above id)

首先,一個類是否有方法或它的類結構是什么與可變性無關。

intfloat不可變的 如果我做

a = 1
a += 5

它指向的名字a1某處內存在第一行。 在第二行,它查找1 ,加上5 ,得到6 ,然后將a指向內存中的6 —— 它沒有以任何方式將1更改6 相同的邏輯適用於以下示例,使用其他不可變類型:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

對於可變類型,我可以做一些實際更改它存儲在 memory 中的值的事情。 和:

d = [1, 2, 3]

我在內存中創建了123的位置列表。 如果我那么做

e = d

我只是將e指向同一個list d指向。 然后我可以這樣做:

e += [4, 5]

並且ed指向的列表將被更新為在內存中也有45的位置。

如果我回到不可變類型並使用tuple執行此操作:

f = (1, 2, 3)
g = f
g += (4, 5)

然后f仍然只指向原始tuple - 你已經將g指向了一個全新的tuple

現在,以你的例子

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

你經過的地方

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

(這是一個tupletuples )作為val ,你得到一個錯誤,因為tuple沒有.clear()方法——你必須將dict(d)作為val傳遞才能工作,在在這種情況下,您將得到一個空的SortedKeyDict結果。

可變對象和不可變對象的區別

定義

可變對象:創建后可以更改的對象。
不可變對象:創建后無法更改的對象。

在 python 中,如果更改不可變對象的值,它將創建一個新對象。

可變對象

以下是 Python 中可變類型的對象:

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

不可變對象

以下是 Python 中不可變類型的對象:

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

一些懸而未決的問題

問題字符串是不可變類型嗎?
的,但你能解釋一下嗎:證明 1

a = "Hello"
a +=" World"
print a

輸出

"Hello World"

在上面的示例中,字符串曾經被創建為“Hello”,然后更改為“Hello World”。 這意味着該字符串是可變類型的。 但是當我們檢查它的身份以查看它是否是可變類型時,它不是。

a = "Hello"
identity_a = id(a)
a += " World"
new_identity_a = id(a)
if identity_a != new_identity_a:
    print "String is Immutable"

輸出

String is Immutable

證明2

a = "Hello World"
a[0] = "M"

輸出

TypeError 'str' object does not support item assignment

問題元組是不可變類型嗎?
回答是的,是的。 證明1

tuple_a = (1,)
tuple_a[0] = (2,)
print a

輸出

'tuple' object does not support item assignment

如果您是從另一種語言(除了與 Python 非常相似的語言,如 Ruby)接觸 Python,並堅持使用另一種語言來理解它,那么人們通常會感到困惑:

>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!

在 Python 中,賦值不是 Python 中的變異。

在 C++ 中,如果你寫a = 2 ,你就是在調用a.operator=(2) ,它會改變存儲在a的對象。 (如果沒有存儲在對象a ,這是一個錯誤。)

在 Python 中, a = 2對存儲在a中的內容沒有任何作用; 它只是意味着2現在存儲在a (如果沒有存儲在對象a ,那很好。)


歸根結底,這是更深層次區別的一部分。

像 C++ 這樣的語言中的變量是內存中的類型化位置。 如果aint ,則意味着它是編譯器知道應該被解釋為int的 4 個字節。 因此,當您執行a = 2 ,它會將這 4 個字節的內存中存儲的內容從0, 0, 0, 1更改為0, 0, 0, 2 如果其他地方有另一個 int 變量,它有自己的 4 個字節。

像 Python 這樣的語言中的變量是具有自己生命周期的對象的名稱。 數字1有一個對象,數字2另一個對象。 並且a不是表示為int 4 個字節的內存,它只是一個指向1對象的名稱。 a = 2將數字 1 變成數字 2 是沒有意義的(這會給任何 Python 程序員太多的權力來改變宇宙的基本運作); 它的作用,而不是僅僅做a忘記了1在對象和點2的對象來代替。


那么,如果賦值不是突變,那么什么突變呢?

  • 調用一個記錄在案的方法來改變,比如a.append(b) (請注意,這些方法幾乎總是返回None )。 不可變類型沒有任何這樣的方法,可變類型通常有。
  • 分配給對象的一部分,如a.spam = ba[0] = b 不可變類型不允許分配給屬性或元素,可變類型通常允許其中之一。
  • 有時使用增廣賦值,如a += b ,有時不使用。 可變類型通常會改變值; 不可變類型從不這樣做,而是給你一個副本(他們計算a + b ,然后將結果分配給a )。

但是如果賦值不是變異,那么如何賦值給對象變異的一部分呢? 這就是它變得棘手的地方。 a[0] = b發生變異a[0]再次,與C ++),但它確實發生變異a (不像C ++,除了間接地)。

所有這一切就是為什么最好不要嘗試將 Python 的語義置於您習慣的語言的角度,而是根據 Python 的語義學習它們自己的術語。

一個對象是否可變取決於它的類型。 這不取決於它是否具有某些方法,也不取決於類層次結構的結構。

用戶定義的類型(即類)通常是可變的。 有一些例外,例如不可變類型的簡單子類。 其他不可變類型包括一些內置類型,例如intfloattuplestr ,以及一些用 C 實現的 Python 類。

Python 語言參考中“數據模型”一章的一般解釋:

某些對象的值可能會發生變化。 值可以改變的對象被稱為可變的; 其值一旦創建就不可更改的對象稱為不可變的。

(包含對可變對象的引用的不可變容器對象的值可以在后者的值改變時改變;但是容器仍然被認為是不可變的,因為它包含的對象集合不能改變。因此,不可變性並不是嚴格意義上的與具有不可更改的值相同,它更微妙。)

一個對象的可變性是由它的類型決定的; 例如,數字、字符串和元組是不可變的,而字典和列表是可變的。

一個可變對象必須至少有一個能夠改變對象的方法。 例如, list對象有append方法,它實際上會改變對象:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

但是float類沒有改變 float 對象的方法。 你可以做:

>>> b = 5.0 
>>> b = b + 0.1
>>> b
5.1

=操作數不是方法。 它只是在變量和它右邊的任何東西之間進行綁定,沒有別的。 它永遠不會改變或創建對象。 從現在開始,它是變量將指向的內容的聲明。

當你做b = b + 0.1=操作數將變量綁定到一個新的浮點數,這是用5 + 0.1 te 結果創建的。

當您將變量分配給現有對象時,無論是否可變, =操作數都會將該變量綁定到該對象。 沒有更多的事情發生

在任何一種情況下, =只是進行綁定。 它不會更改或創建對象。

當您執行a = 1.0=操作數不是創建浮點數,而是該行的1.0部分。 實際上,當您編寫1.0它是float(1.0)構造函數調用返回浮點對象的簡寫。 (這就是為什么如果您鍵入1.0並按回車鍵,您會得到下面打印的“echo” 1.0 ;這是您調用的構造函數的返回值)

現在,如果b是浮點數並且您分配a = b ,則兩個變量都指向同一個對象,但實際上變量無法相互通信,因為該對象是不可變的,如果您執行b += 1 ,現在b指向一個新對象,而a仍然指向舊對象,無法知道b指向什么。

但如果c是,比方說,一個list ,並分配a = c ,現在ac可以“comunicate”,因為list是可變的,如果你這樣做c.append('msg')然后只檢查a你得到消息。

(順便說一下,每個對象都有一個唯一的 id 編號,您可以通過id(x) 。因此您可以檢查對象是否相同或不檢查其唯一 id 是否已更改。)

如果該類的每個對象在實例化時具有固定值且不能隨后更改,則該類是不可變的

換句話說,更改該變量(name)的整個值或不理會它。

例子:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

您希望它可以工作並打印hello world但這會引發以下錯誤:

Traceback (most recent call last):
File "test.py", line 4, in <module>
my_string[0] = "h"
TypeError: 'str' object does not support item assignment

解釋器說:我不能改變這個字符串的第一個字符

您必須更改整個string才能使其正常工作:

my_string = "Hello World" 
my_string = "hello world"
print my_string #hello world

檢查此表:

在此處輸入圖片說明

來源

在我看來,您正在與 mutable/immutable 實際上意味着什么這個問題作斗爭 所以這里有一個簡單的解釋:

首先,我們需要一個基礎來進行解釋。

所以把你編程的任何東西想象成一個虛擬對象,一些作為二進制數序列保存在計算機內存中的東西。 (不過不要想得太難。^^)現在在大多數計算機語言中,您不會直接使用這些二進制數,而是更多地使用二進制數的解釋。

例如,您不會考慮像 0x110、0xaf0278297319 或類似的數字,而是考慮像 6 這樣的數字或像“Hello, world”這樣的字符串。 無論如何,這些數字或字符串都是對計算機內存中二進制數的解釋。 對於變量的任何值也是如此。

簡而言之:我們使用實際值編程,而是使用實際二進制值的解釋。

現在我們確實有出於邏輯和其他“整潔的東西”而不能改變的解釋,而有些解釋很可能會改變。 例如,想想模擬一個城市,換句話說,一個有許多虛擬對象的程序,其中一些是房屋。 現在這些虛擬對象(房子)可以被改變,它們仍然可以被認為是相同的房子嗎? 他們當然可以。 因此它們是可變的:它們可以被改變而不會變成“完全”不同的對象。

現在想想整數:它們也是虛擬對象(計算機內存中的二進制數序列)。 因此,如果我們更改其中之一,例如將值 6 加一,它仍然是 6 嗎? 當然不是。 因此任何整數都是不可變的。

所以:如果一個虛擬對象的任何變化意味着它實際上變成了另一個虛擬對象,那么它被稱為不可變的。

最后說明:

(1) 永遠不要將可變和不可變的真實體驗與使用某種語言的編程混淆:

每種編程語言都有自己的定義,哪些對象可以靜音,哪些可以不靜音。

因此,雖然您現在可能理解了含義上的差異,但您仍然需要了解每種編程語言的實際實現。 ...確實可能有一種語言的目的是將 6 靜音變成 7。然后,這將是一些非常瘋狂或有趣的東西,例如平行宇宙的模擬。^^

(2) 這個解釋當然不科學,是為了幫助你掌握可變和不可變的區別。

這個答案的目標是創建一個地方來找到所有關於如何判斷您是否正在處理變異/非變異(不可變/可變)的好主意,以及在可能的情況下如何處理? 有時突變是不可取的,python 在這方面的行為對於從其他語言進入它的編碼人員來說可能會違反直覺。

根據@mina-gabriel 的有用帖子:

分析上述內容並結合@arrakën 的帖子:

什么不能意外改變?

  • 標量(存儲單個值的變量類型)不會意外更改
    • 數字示例:int()、float()、complex()
  • 有一些“可變序列”:
    • str()、tuple()、frozenset()、bytes()

什么可以?

  • 類似對象的列表(列表、字典、集合、bytearray())
  • 這里的一篇文章也說類和類實例,但這可能取決於類繼承自什么和/或它是如何構建的。

“意外”我的意思是來自其他語言的程序員可能不會期望這種行為(除了 Ruby 或其他一些“類似 Python”的語言)。

添加到這個討論:

這種行為是一個優勢,因為它可以防止您意外地將占用內存的大型數據結構的多個副本填充到您的代碼中。 但是,如果這是不受歡迎的,我們如何解決它?

對於列表,簡單的解決方案是像這樣構建一個新的:

列表 2 = 列表(列表 1)

使用其他結構......解決方案可能會更棘手。 一種方法是遍歷元素並將它們添加到一個新的空數據結構(相同類型)。

當您傳入可變結構時,函數可以改變原始函數。 怎么講?

  • 對該線程上的其他評論進行了一些測試,但有評論表明這些測試不是完全證明
  • object.function() 是原始對象的一種方法,但只有其中一些會發生變化。 如果他們什么都不返回,他們可能會這樣做。 人們會期望 .append() 在沒有對其名稱進行測試的情況下發生變異。 .union() 返回 set1.union(set2) 的並集並且不會發生變異。 如有疑問,可以檢查該函數的返回值。 如果 return = None,則不會發生變異。
  • sorted() 在某些情況下可能是一種解決方法。 由於它返回原始版本的排序版本,因此它可以讓您在以其他方式開始處理原始版本之前存儲未變異的副本。 但是,此選項假定您不關心原始元素的順序(如果您這樣做,則需要找到另一種方式)。 相比之下, .sort() 改變了原始的(正如人們所期望的)。

非標准方法(如果有幫助):在 MIT 許可下發布的 github 上找到了這個:

  • github 存儲庫下: tobgu 命名: pyrsistent
  • 它是什么:Python 持久數據結構代碼編寫用於在不希望發生突變時代替核心數據結構

對於自定義類,@semicolon 建議檢查是否有__hash__函數,因為可變對象通常不應該有__hash__()函數。

這就是我目前在這個主題上積累的全部內容。 歡迎其他想法、更正等。 謝謝。

一種思考差異的方式:

在python中對不可變對象的賦值可以被認為是深拷貝,而對可變對象的賦值是淺的

最簡單的答案:

可變變量是其值可以就地更改的變量,而在不可變變量中,值的更改不會就地發生。 修改不可變變量將重建相同的變量。

例子:

>>>x = 5

將創建一個由 x 引用的值 5

x -> 5

>>>y = x

此語句將使 y 指代 x 中的 5

x -------------> 5 <-----------y

>>>x = x + y

因為 x 是一個整數(不可變類型)已經被重建。

在語句中,RHS 上的表達式將導致值 10,當它分配給 LHS (x) 時,x 將重建為 10。所以現在

x--------->10

y--------->5

可變意味着它可以改變/變異 不可變的相反。

有些 Python 數據類型是可變的,有些則不是。

讓我們找出適合每個類別的類型並查看一些示例。


可變的

在 Python 中有各種可變類型:

  • 列表

  • 字典

讓我們看看下面的lists示例。

list = [1, 2, 3, 4, 5]

如果我執行以下操作來更改第一個元素

list[0] = '!'
#['!', '2', '3', '4', '5']

它工作得很好,因為列表是可變的。

如果我們考慮該列表,該列表已更改,並為其分配一個變量

y = list

如果我們更改列表中的一個元素,例如

list[0] = 'Hello'
#['Hello', '2', '3', '4', '5']

如果一個打印y它會給

['Hello', '2', '3', '4', '5']

由於listy指的是同一個列表,我們已經更改了列表。


不可變

在某些編程語言中,可以定義一個常量,如下所示

const a = 10

如果有人打電話,它會給出一個錯誤

a = 20

但是,這在 Python 中不存在。

然而,在 Python 中,有多種不可變類型:

  • 沒有任何

  • 布爾值

  • 整數

  • 漂浮

  • 字符串

  • 元組

讓我們看看下面的strings示例。

取字符串a

a = 'abcd'

我們可以得到第一個元素

a[0]
#'a'

如果試圖為第一個位置的元素分配一個新值

a[0] = '!'

它會報錯

“str”對象不支持項目分配

當對字符串說 += 時,例如

a += 'e'
#'abcde'

它不給一個錯誤,因為它指向a以不同的字符串。

這將與以下相同

a = a + 'f'

並且不改變字符串。

不可變的一些優點和缺點

• 從一開始就知道內存中的空間。 它不需要額外的空間。

• 通常,它使事情更有效率。 例如,查找字符串的len()會快得多,因為它是字符串對象的一部分。

我還沒有閱讀所有答案,但所選答案不正確,我認為作者有一個想法,即能夠重新分配變量意味着任何數據類型都是可變的。 事實並非如此。 可變性與按引用傳遞而不是按值傳遞有關。

假設您創建了一個列表

a = [1,2]

如果你要說:

b = a
b[1] = 3

即使您在 B 上重新分配了一個值,它也會重新分配 a 上的值。 這是因為當您分配“b = a”時。 您將“引用”傳遞給對象而不是值的副本。 字符串、浮點數等不是這種情況。這使得列表、字典等可變,但布爾值、浮點數等不可變。

在 Python 中,有一種簡單的方法可以了解:

不可變:

    >>> s='asd'
    >>> s is 'asd'
    True
    >>> s=None
    >>> s is None
    True
    >>> s=123
    >>> s is 123
    True

可變:

>>> s={}
>>> s is {}
False
>>> {} is {}
Flase
>>> s=[1,2]
>>> s is [1,2]
False
>>> s=(1,2)
>>> s is (1,2)
False

和:

>>> s=abs
>>> s is abs
True

所以我認為內置函數在 Python 中也是不可變的。

但我真的不明白浮動是如何工作的:

>>> s=12.3
>>> s is 12.3
False
>>> 12.3 is 12.3
True
>>> s == 12.3
True
>>> id(12.3)
140241478380112
>>> id(s)
140241478380256
>>> s=12.3
>>> id(s)
140241478380112
>>> id(12.3)
140241478380256
>>> id(12.3)
140241478380256

這太奇怪了。

例如,對於不可變對象,賦值會創建值的新副本。

x=7
y=x
print(x,y)
x=10 # so for immutable objects this creates a new copy so that it doesnot 
#effect the value of y
print(x,y)

對於可變對象,賦值不會創建值的另一個副本。 例如,

x=[1,2,3,4]
print(x)
y=x #for immutable objects assignment doesn't create new copy 
x[2]=5
print(x,y) # both x&y holds the same list

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM