python變量和變量賦值的幾種形式

動態類型的語言

python是動態類型的語言,不須要聲明變量的類型。php

實際上,python中的變量僅僅只是用來保存一個數據對象的地址。不管是什麼數據對象,在內存中建立好數據對象以後,都只是把它的地址保存到變量名中。因此變量名是類型無關的,但它指向的值是類型相關的,能夠是數值、字符串、列表、函數、類、對象等等。這些內存對象中都至少包含3部分:對象類型、對象的引用計數(用來判斷改對象是否可被垃圾回收器回收)、對象的值python

所以,a = 3中,變量名a保存的是數據對象3的地址,以後能夠爲其賦值一個字符串a = "hello",這時a保存的是"hello"字符串的地址。在這個類型改變的過程當中,a僅僅只是修改了一下地址而已。shell

變量的命名風格

python中的變量命名時只能包含數字、大小寫字母、下劃線這三種類型的字符,而且數字不能是首字符。app

還有一些有特殊意義的變量命名方式(目前這些內容瞭解便可):函數

  1. 前綴並後綴雙下劃線的變量,如__name__,這種類型的變量在python中有特殊意義,屬於對象的內置屬性,之後學了類和對象就知道了
  2. 單下劃線前綴的變量,如_x,這類變量不會被from ModuleName import *的方式導入
  3. 雙下劃線前綴的變量,如__x,這類變量是類的本地變量或稱爲類的私有變量,它會擴展成__classname_x

除此以外,還有約定俗成的命名方式:性能

  1. 常量以全大寫字符表示
  2. 普通變量、函數名、方法名都以小寫字母開頭命名
  3. 模塊名、包名以全小寫字母命名
  4. 類名以大寫字母開頭

由於只是約定俗成,因此沒有強制限制。優化

變量賦值的幾種形式細節

本文解釋python中變量賦值的形式,並解釋一些細節。後面還有一篇文章解釋python中按引用賦值的文章。code

python中變量賦值的幾種形式。對象

x = "long"                  # (1).基本形式
x, y = "long", "shuai"      # (2).元組對應賦值
[x, y] = ["long", "shuai"]  # (3).列表對應賦值
a, b, c, d = "long"         # (4).序列賦值
a, *b = 'long'              # (5).解包賦值
a = b = "long"              # (6).多目標賦值
a += 3                      # (7).二元賦值表達式
((a, b), c) = ('lo','ng')   # (8).嵌套賦值序列

注意:python的數值是不可變對象,沒法在原處修改數據,因此不支持自增、自減blog

a++
++a
a--
--b

其中(1)-(3)無需過多解釋,惟一須要注意的是,當使用逗號的時候,python總會臨時或永久地創建成tuple來保存元素,因此x, y = "long", "shuai"在內部徹底等價於(x, y) = ("long", "shuai")

實際上,列表元素也能夠賦值給元組,或者元組賦值給列表,只要兩邊的序列元素個數能對應,無所謂左右兩邊的序列類型:

>>> (x,y) = (1,2)
>>> (x,y) = [1,2]
>>> [x,y] = (1,2)
>>> [x,y] = [1,2]
>>> (x,y) = 'ab'
>>> [x,y] = 'ab'

對於(4)賦值方式,是序列賦值的行爲,在python中,只要是序列,均可以這樣賦值。正如這裏的變量賦值狀況等價於:

a = "l"
b = "o"
c = "n"
d = "g"

若是換成其它的序列也同樣。例如:

a, b, c, d = ("shell","perl","php","python")
a, b, c, d = ["shell","perl","php","python"]

可是變量和序列中的元素必須一一對應。若是變量名與元素個數不一樣,則會報錯,除非只有一個變量名,這表示將整個序列賦值給這個變量。

若是想要將序列中的元素賦值給不等的變量,能夠考慮先將序列進行切片。

>>> str='long'
>>> a, b, c = list(str[:2]) + [str[2:]]
>>> a,b,c
('l', 'o', 'ng')

(5)的賦值方式則正好是讓變量名少於元素個數的方式。這種賦值形式稱爲序列解包(下文會專門解釋這種賦值方式),多出來的元素會所有以列表的方式賦值給最後一個變量名。正如這裏等價於:

a="l"
b=["o", "n", "g"]

下面兩種賦值方式獲得的結果是同樣的,a是字符串,b是列表,b都包含3個元素:

a, *b = ("shell","perl","php","python")
a, *b = ["shell","perl","php","python"]

賦值的結果:

shell
['perl', 'php', 'python']

(6)的賦值方式等價於:

b = "long"
a = b

python賦值時,老是先計算"="右邊的結果,而後將結果按照賦值方式賦值給"="左邊的變量。因此,這裏的過程是先將"long"賦值給變量b,再將b賦值給變量a。

由於老是先計算右邊,因此交換變量很是的方便。

a, b = "a", "b"

# 交換:
a, b = b, a

# 交換結果:
a = "b"
b = "a"

(7)的賦值方式a += 3在結果上等價於a = a + 3,在其它語言中這兩種賦值方式是徹底等價的,但在python中這種加強賦值的方式要比後者更高效率些,爲何效率要高一些,下文會稍做解釋。在很大程度上來講,Python中只要是簡化的形式,基本上都比更復雜的等價形式效率更高。

(8)的賦值方式((a, b), c) = ('lo', 'ng')是將序列進行嵌套序列賦值,將'lo'賦值給元組(a, b),'ng'賦值給c,元組又進一步賦值a='l', b='o'。這種賦值方式在python中很好用,特別是在表達式中賦值的時候,好比for循環和函數參數:

for (a, b, c) in [(1, 2, 3), (4, 5, 6)]:...
for ((a, b), c) in [((1, 2), 3), ((4, 5), 6)]:...

def f(((a, b), c)):...
f(((1, 2), 3))

關於序列解包

在前面簡單介紹了一下序列解包:

a, *b = 'long'

當使用一個*前綴變量的時候,表示將序列對應的元素所有收集到一個列表中(注意,老是一個列表),這個列表名爲*開頭的那個變量名。*號能夠出如今任意位置處,只要賦值的時候能先後對應位置關係便可。

注意其中的幾個關鍵字:序列、對應的元素、列表

  • 序列意味着能夠是列表、元組、字符串等等
  • 列表意味着只要收集不報錯,賦值給解包變量的必定是一個列表
  • 對應的元素意味着可能收集到0或任意個元素到列表。

無論如何,收集的結果老是列表,只不過多是空列表或者只有一個元素的列表。

例如:

L = ['aa','bb','cc','dd']
a, *b = L           # a='aa',b=['bb','cc','dd']
a, b, *c = L        # a='aa',b='bb',c=['cc','dd']
a,*b,d = L          # a='aa',d='dd',b=['bb','cc']
*a,d = L            # a=['aa','bb','cc'],d='dd'
a,b,c,*d = L        # a='aa',b='bb',c='cc',d=['dd']
a,b,c,d,*e = L      # a='aa',b='bb',c='cc',d='dd',e=[]

兩個注意事項:

  1. 由於序列解包是根據元素位置來進行賦值的,因此不能出現多個解包變量
  2. 若是將序列直接賦值給單個解包變量時(即沒有普通變量),這個解包變量必須放在列表或元組中
a,*b,c,*d = L     # 錯誤
*a = L            # 錯誤
[*a] = L          # 正確
(*a) = L          # 正確

之因此單個解包變量時必須放在元組或變量中,看下面兩個等價的例子就很容易理解了:

a, *b = L
(a, *b) = L

最後,序列解包是切片的便捷替代方式。能用序列解包的,都能用切片來實現,但切片要輸入額外的各類字符。例如:

a,b,c = L[0],L[1],L[2:]
a,b,*c = L

須要注意,解包返回的必定是列表,但序列切片返回的內容則取決於序列的類型。例以下面元組的切片返回的是元組,而不是列表:

>>> T=('aa','bb','cc','dd')
>>> a,b,c = T[0],T[1],T[2:]
>>> a,b,c
('aa', 'bb', ('cc', 'dd'))

二元賦值表達式

python支持相似於a += 3這種二元表達式。好比:

a += 3   ->   a = a + 3
a -= 3   ->   a = a - 3
a *= 3   ->   a = a * 3
...

在python中的某些狀況下,這種二元賦值表達式可能比普通的賦值方式效率更高些。緣由有二:

  1. 二元賦值表達式中,a可能會是一個表達式,它只需計算評估一次,而a = a + 3中,a要計算兩次。
  2. 對於可變對象,能夠直接在原處修改獲得修改後的值,而普通的一元賦值表達式必須在內存中新建立一個修改後的數據對象,並賦值給變量

第一點無需解釋。關於第二點,看下面的例子:

L = [1,2,3]
L = L + [4]     # (1):慢
L += [4]        # (2):快
L.append(4)     # (3):快,等價於(2)

L = L + [5,6]   # (4):慢
L += [5,6]      # (5):快
L.extend([5,6]) # (6):快,等價於(5)

對於上面(1)和(4)的一元賦值表達式,先取得L,而後建立一個新的列表對象,將L拷貝到新列表對象中,並將45,6放進新列表對象,最後賦值給L。這個過程當中涉及到了幾個步驟:新分配內存、內存中列表拷貝、放入新數據。

而(2)(3)是等價的,(5)(6)也是等價的,它們都是直接在內存中的原始列表處修改,不會有拷貝操做,新建的數據對象僅僅只是一個元素。

按照理論上來講,確實二元賦值方式要效率高一些,但要注意的是,列表中保存的只是各元素的引用,因此拷貝列表也僅僅只是拷貝一點引用,這是微乎其微的開銷。因此一元賦值和二元賦值的差距在這一點的性能上基本沒差距,主要的差距還在於一元、二元賦值方式可能存在的表達式不一樣評估次數。

總的來講,使用二元賦值表達式一般能夠做爲可變對象賦值的一種優化手段

相關文章
相關標籤/搜索