Python進(jìn)階 - 對(duì)象,名字以及綁定

寫在前面

如非特別說(shuō)明,下文均基于Python3

1、一切皆對(duì)象

Python哲學(xué):

Python中一切皆對(duì)象

1.1 數(shù)據(jù)模型-對(duì)象,值以及類型

對(duì)象是Python對(duì)數(shù)據(jù)的抽象。Python程序中所有的數(shù)據(jù)都是對(duì)象或?qū)ο笾g的關(guān)系表示的。(在某種意義上,為順應(yīng)馮·諾依曼“存儲(chǔ)式計(jì)算機(jī)”的模型,Python中的代碼也是對(duì)象。)

Python中每一個(gè)對(duì)象都有一個(gè)身份標(biāo)識(shí),一個(gè)值以及一個(gè)類型。對(duì)象創(chuàng)建后,其身份標(biāo)識(shí)絕對(duì)不會(huì)改變;可以把身份標(biāo)識(shí)當(dāng)做對(duì)象在內(nèi)存中的地址。is操作符比較兩個(gè)對(duì)象的身份標(biāo)識(shí);id()函數(shù)返回表示對(duì)象身份標(biāo)識(shí)的整數(shù)。

CPython實(shí)現(xiàn)細(xì)節(jié):CPython解釋器的實(shí)現(xiàn)中,id(x)函數(shù)返回存儲(chǔ)x的內(nèi)存地址

對(duì)象的類型決定了對(duì)象支持的操作(例如,對(duì)象有長(zhǎng)度么?),同時(shí)也決定了該類型對(duì)象可能的值。type()函數(shù)返回對(duì)象的類型(這個(gè)類型本身也是一個(gè)對(duì)象)。與其身份標(biāo)識(shí)一樣,對(duì)象的類型也是不可改變的[1]。

一些對(duì)象的值可以改變??筛淖冎档膶?duì)象也稱作可變的(mutable);一旦創(chuàng)建,值恒定的對(duì)象也叫做 不可變的(immutable)。(當(dāng)不可變?nèi)萜鲗?duì)象中包含對(duì)可變對(duì)象的引用時(shí),可變對(duì)象值改變時(shí),這個(gè)不可變?nèi)萜鲗?duì)象值也被改變了;然而,不可變?nèi)萜鲗?duì)象仍被認(rèn)為是不可變的,因?yàn)閷?duì)象包含的值集合確實(shí)是不可改變的。因此,不可變性不是嚴(yán)格等同于擁有不可變的值,它很微妙。) (譯注:首先不可變?nèi)萜鲗?duì)象的值是一個(gè)集合,集合中包含了對(duì)其他對(duì)象的引用;那么這些引用可以看做地址,即使地址指向的內(nèi)容改變了,集合中的地址本身是沒(méi)有改變的。所以不可變?nèi)萜鲗?duì)象還是不可變對(duì)象。) 對(duì)象的可變性取決于其類型;例如,數(shù)字,字符串和元組是不可變的,但字典和列表是可變的。

對(duì)象從不顯式銷毀;當(dāng)對(duì)象不可達(dá)時(shí)會(huì)被垃圾回收(譯注:對(duì)象沒(méi)有引用了)。一種解釋器實(shí)現(xiàn)允許垃圾回收延時(shí)或者直接忽略——這取決于垃圾回收是如何實(shí)現(xiàn)的,只要沒(méi)有可達(dá)對(duì)象被回收。

CPython實(shí)現(xiàn)細(xì)節(jié): CPython解釋器實(shí)現(xiàn)使用引用計(jì)數(shù)模式延時(shí)探測(cè)循環(huán)鏈接垃圾,這種方式可回收大多數(shù)不可達(dá)對(duì)象,但并不能保證循環(huán)引用的垃圾被回收。查看gc模塊的文檔了解控制循環(huán)垃圾回收的更多信息。其他解釋器實(shí)現(xiàn)與CPython不同,CPython實(shí)現(xiàn)將來(lái)也許會(huì)改變。因此不能依賴?yán)厥掌鱽?lái)回收不可達(dá)對(duì)象(因此應(yīng)該總是顯式關(guān)閉文件對(duì)象。)。

需要注意,使用工具的調(diào)試跟蹤功能可能會(huì)導(dǎo)致應(yīng)該被回收的對(duì)象一直存活,使用try...except語(yǔ)句捕獲異常也可以保持對(duì)象的存活。

一些對(duì)象引用了如文件或者窗口的外部資源。不言而喻持有資源的對(duì)象被垃圾回收后,資源也會(huì)被釋放,但因?yàn)闆](méi)有機(jī)制保證垃圾回收一定會(huì)發(fā)生,這些資源持有對(duì)象也提供了顯式釋放外部資源的方式,通常使用close()方法。強(qiáng)烈推薦在程序中顯式釋放資源。try...finally語(yǔ)句和with語(yǔ)句為釋放資源提供了便利。

一些對(duì)象包含對(duì)其他對(duì)象的引用,這些對(duì)象被稱作 容器。元組,列表和字典都是容器。引用的容器值的一部分。大多數(shù)情況下,談?wù)撊萜鞯闹禃r(shí),我們暗指容器包含的對(duì)象值集合,而不是對(duì)象的身份標(biāo)識(shí)集合;然而,談?wù)撊萜鞯目勺冃詴r(shí),我們暗指容器包含的對(duì)象的身份標(biāo)識(shí)。因此,如果不可變對(duì)象(如元組)包含對(duì)可變對(duì)象的引用,可變對(duì)象改變時(shí),其值也改變了。

類型影響對(duì)象的絕大多數(shù)行為。在某些情況下甚至對(duì)象的身份標(biāo)識(shí)的重要性也受到影響:對(duì)于不可變類型,計(jì)算新值的操作實(shí)際上可能會(huì)返回已存在的,值和類型一樣的對(duì)象的引用,然而對(duì)于可變對(duì)象來(lái)說(shuō)這是不可能的。例如,語(yǔ)句a = 1; b = 1執(zhí)行之后,ab可能會(huì)也可能不會(huì)引用具有相同值得同一個(gè)對(duì)象,這取決于解釋器實(shí)現(xiàn)。但是語(yǔ)句c = []; d = []執(zhí)行之后,可以保證cd會(huì)指向不同的,唯一的新創(chuàng)建的空列表。(注意 c = d = []分配相同的對(duì)象給cd)

Note: 以上翻譯自 《The Python Language References#Data model# Objects, values, types》 3.6.1版本。

1.2 對(duì)象小結(jié)

官方文檔已經(jīng)對(duì)Python 對(duì)象做了詳細(xì)的描述,這里總結(jié)一下。

對(duì)象的三個(gè)特性:

  • 身份標(biāo)識(shí)
    唯一標(biāo)識(shí)對(duì)象;不可變;CPython解釋器實(shí)現(xiàn)為對(duì)象的內(nèi)存地址。
    操作:id(),內(nèi)建函數(shù)id()函數(shù)返回標(biāo)識(shí)對(duì)象的一個(gè)整數(shù);is比較兩個(gè)對(duì)象的身份標(biāo)識(shí)。
    示例:

      >>> id(1)
      1470514832
      >>> 1 is 1
      True
    
  • 類型
    決定對(duì)象支持的操作,可能的值;不可變。
    操作:type(),內(nèi)建函數(shù)返回對(duì)象的類型
    示例:

      >>> type('a')
      <class 'str'>
    

  • 數(shù)據(jù),可變/不可變
    操作:==操作符用于比較兩個(gè)對(duì)象的值是否相等,其他比較運(yùn)算符比較對(duì)象間大小情況。
    示例:

      >>> 'python'
      'python'
      >>> 1 == 2
      False
    

可變與不可變:一般認(rèn)為,值不可變的對(duì)象是不可變對(duì)象,值可變的對(duì)象是可變對(duì)象,但是要注意不可變集合對(duì)象包含可變對(duì)象引用成員的情況。

Python中的對(duì)象:

# -*- coding: utf-8 -*-
# filename: hello.py

'a test module'

__author__ = 'Richard Cheng'

import sys

class Person(object):
    ''' Person class'''

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


def tset():
    print(sys.path)
    p = Person('Richard', 20)
    print(p.name, ':', p.age)

def main():
    tset()

if __name__ == '__main__':
    main()

這段Python代碼中有很多對(duì)象,包括hello這個(gè)模塊對(duì)象,創(chuàng)建的Person類對(duì)象,幾個(gè)函數(shù)如test, main函數(shù)對(duì)象,數(shù)字,字符串,甚至代碼本身也是對(duì)象。

2、名字即“變量”

幾乎所有語(yǔ)言中都有“變量”的說(shuō)法,嚴(yán)格說(shuō)來(lái),Python中的變量不應(yīng)該叫變量,稱為名字更加貼切。

以下翻譯自 Code Like a Pythonista: Idiomatic Python # Python has "names"

2.1 其他語(yǔ)言有變量

其他語(yǔ)言中,為變量分配值就像將值放到“盒子”里。
int a = 1;

把1放到盒子里

盒子a現(xiàn)在有了一個(gè)整數(shù)1

為同一個(gè)變量分配值替換掉盒子的內(nèi)容:
a =2;

把2放到同一個(gè)盒子里

現(xiàn)在盒子a中放了整數(shù)2

將一個(gè)變量分配給另一個(gè)變量,拷貝變量的值,并把它放到新的盒子里:
int b = a;

b盒子里放了2
a盒子里也放了

b是第二個(gè)盒子,裝有整數(shù)2的拷貝。盒子a有一份單獨(dú)的拷貝。

2.2 Python有名字

Python中,名字或者標(biāo)識(shí)符就像將一個(gè)標(biāo)簽捆綁到對(duì)象上一樣。
a = 1

將名字a捆綁到對(duì)象1上

這里,整數(shù)對(duì)象1有一個(gè)叫做a的標(biāo)簽。

如果重新給a分配值,只是簡(jiǎn)單的將標(biāo)簽移動(dòng)到另一個(gè)對(duì)象:
a = 2

標(biāo)簽a移動(dòng)到另一個(gè)對(duì)象

1對(duì)象沒(méi)有標(biāo)簽了

現(xiàn)在名字a貼到了整數(shù)對(duì)象2上面。原來(lái)的整數(shù)對(duì)象1不再擁有標(biāo)簽a,或許它還存在,但是不能通過(guò)標(biāo)簽a訪問(wèn)它了(當(dāng)對(duì)象沒(méi)有任何引用時(shí),會(huì)被回收。)

如果將一個(gè)名字分配給另一名字,只是將另一個(gè)名字標(biāo)簽捆綁到存在的對(duì)象上:
b = a

有兩個(gè)標(biāo)簽的對(duì)象

名字b只是綁定到與a引用的相同對(duì)象上的第二個(gè)標(biāo)簽而已。

雖然在Python中普遍使用“變量”(因?yàn)椤白兞俊笔瞧毡樾g(shù)語(yǔ)),真正的意思是名字或者標(biāo)識(shí)符。Python中的變量是值得標(biāo)簽,不是裝值得盒子。

2.3 指針?引用?名字?

C/C++中有指針,Java中有引用,Python中的名字在一定程度上等同于指針和引用。

2.1節(jié)中其他語(yǔ)言的例子,也只是針對(duì)于它們的基本類型而言的,若是指針或者引用,表現(xiàn)也跟Python的名字一樣。這也在一定程度上說(shuō)明了Python將面向?qū)ο筘瀼氐酶訌氐住?/p>

2.4 名字支持的操作

可以對(duì)一個(gè)變量做什么?聲明變量,使用變量,修改變量的值。名字作為Python中的一個(gè)重要概念,可以對(duì)它做的操作有:

  • 定義;名字需要先定義才能使用,與變量需要先聲明一樣。
  • 綁定:名字的單獨(dú)存在沒(méi)有意義,必須將它綁定到一個(gè)對(duì)象上。
  • 重綁定:名字可以重新引用另一個(gè)對(duì)象,這個(gè)操作就是重綁定。
  • 引用:為什么要定義名字,目的是使用它。

3、綁定的藝術(shù)

名字以及對(duì)象,它們之間必然會(huì)發(fā)生些什么。

3.1 變量的聲明

其他如C/C++Java的高級(jí)語(yǔ)言,變量在使用前需要聲明,或者說(shuō)定義。以下在Java中聲明變量:

public static void main(String[] args) {
        int i = 0; // 先聲明,后使用
        System.out.println(i); // 使用變量i
}

這樣,在可以訪問(wèn)到變量i所在作用域的地方,既可以使用i了。還有其他聲明變量的方法么?好像沒(méi)有了。

3.2 名字的定義

Python中有多種定義名字的途徑,如函數(shù)定義,函數(shù)名就是引用函數(shù)對(duì)象的名字;類定義,類名就是指向類對(duì)象的名字,模塊定義,模塊名就是引用模塊對(duì)象的名字;當(dāng)然,最直觀的還是賦值語(yǔ)句。

賦值語(yǔ)句

官方對(duì)賦值語(yǔ)句做了這樣的說(shuō)明(地址):

Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects.

即:

賦值語(yǔ)句被用來(lái)將名字綁定或者重綁定給值,也用來(lái)修改可變對(duì)象的屬性或項(xiàng)

那么,我們關(guān)心的,就是賦值語(yǔ)句將名字和值(對(duì)象)綁定起來(lái)了。

看一個(gè)簡(jiǎn)單的賦值語(yǔ)句:

a = 9

Python在處理這條語(yǔ)句時(shí):

  1. 首先在內(nèi)存中創(chuàng)建一個(gè)對(duì)象,表示整數(shù)9:
整數(shù)9對(duì)象
  1. 然后創(chuàng)建名字a,并把它指向上述對(duì)象:
名字a引用對(duì)象

上述過(guò)程就是通過(guò)賦值語(yǔ)句的名字對(duì)象綁定了。名字首次和對(duì)象綁定后,這個(gè)名字就定義在當(dāng)前命名空間了,以后,在能訪問(wèn)到這個(gè)命名空間的作用域中可以引用該名字了。

3.3 引用不可變對(duì)象

定義完名字之后,就可以使用名字了,名字的使用稱為“引用名字”。當(dāng)名字指向可變對(duì)象和不可變對(duì)象時(shí),使用名字會(huì)有不同的表現(xiàn)。

a = 9       #1
a = a + 1   #2

語(yǔ)句1執(zhí)行完后,名字a指向表示整數(shù)9的對(duì)象:

名字a引用對(duì)象

由于整數(shù)是不可變對(duì)象,所以在語(yǔ)句2處引用名字a,試圖將表示整數(shù)9的對(duì)象 + 1,但該對(duì)象的值是無(wú)法改變的。因此就將該對(duì)象表示的整數(shù)值91,以整數(shù)10新建一個(gè)整數(shù)對(duì)象:

新建表示10的整數(shù)對(duì)象

接下來(lái),將名字a 重綁定 到新建對(duì)象上,并移除名字對(duì)原對(duì)象的引用:

重綁定

使用id()函數(shù),可以看到名字a指向的對(duì)象地址確實(shí)發(fā)生了改變:

>>> a = 9
>>> id(a)
1470514960
>>> a = a + 1
>>> id(a)
1470514976
3.4 引用可變對(duì)象
3.4.1 示例1:改變可變對(duì)象的值

可變對(duì)象可以改變其值,并且不會(huì)造成地址的改變:

>>> list1 = [1]
>>> id(list1)
42695136
>>> list1.append(2)
>>> id(list1)
42695136
>>> list1
[1, 2]
>>> 

執(zhí)行語(yǔ)句list1 = [1],創(chuàng)建一個(gè)list對(duì)象,并且其值集中添加1,將名字list1指向該對(duì)象:

初始集合

執(zhí)行語(yǔ)句list1.append(2),由于list是可變對(duì)象,可以直接在其值集中添加2

添加值

值得改變并沒(méi)有造成list1引用的對(duì)象地址的改變。

3.4.2 示例2:可變對(duì)象循環(huán)引用

再來(lái)看一個(gè)比較“奇怪”的例子:

values = [1, 2, 3]
values[1] = values
print(values)

一眼望去,期待的結(jié)果應(yīng)該是

[1, [1, 2, 3], 3]

但實(shí)際上結(jié)果是:

[1, [...], 3]

我們知道list中的元素可以是各種類型的,list類型是可以的:

循環(huán)引用
3.4.3 示例3:重綁定可變對(duì)象

觀察以下代碼段:

>>> list1 = [1]
>>> id(list1)
42695136
>>> list1 = [1, 2]
>>> id(list1)
42717432

兩次輸出的名字list1引用對(duì)象的地址不一樣,這是因?yàn)榈诙握Z(yǔ)句list 1 = [1, 2] 對(duì)名字做了重綁定:

重綁定
3.5 共享對(duì)象

當(dāng)兩個(gè)或兩個(gè)以上的名字引用同一個(gè)對(duì)象時(shí),我們稱這些名字共享對(duì)象。共享的對(duì)象可變性不同時(shí),表現(xiàn)會(huì)出現(xiàn)差異。

3.5.1 共享不可變對(duì)象

函數(shù)attempt_change_immutable將參數(shù)i的值修改為2

def attempt_change_immutable(i):
    i = 2

i = 1
print(i)
attempt_change_immutable(i)
print(i)

Output:

1
1

如果你對(duì)輸出不感到意外,說(shuō)明不是新手了 _。

  1. 首先,函數(shù)的參數(shù)i與全局名字i不是在同一命名空間中,所以它們之間不相互影響。
  2. 調(diào)用函數(shù)時(shí),將兩個(gè)名字i都指向了同一個(gè)整數(shù)對(duì)象。
  3. 函數(shù)中修改i的值為2, 因?yàn)檎麛?shù)對(duì)象不可變,所以新建值為2的整數(shù)對(duì)象,并把函數(shù)中的名字i綁定到對(duì)象上。
  4. 全局名字i的綁定關(guān)系并沒(méi)有被改變。
兩個(gè)i指向同一個(gè)對(duì)象
改變函數(shù)i的綁定關(guān)系

值得注意的是,這部分內(nèi)容與命名空間和作用域有關(guān)系,另外有文章介紹它們,可以參考。

3.5.2 共享可變對(duì)象

函數(shù)attempt_change_mutable為列表增加字符串。

def attempt_change_mutable(list_param):
    list_param.append('test')

list1 = [1]
print(list1)
attempt_change_mutable(list1)
print(list1)

output:

[1]
[1, 'test']

可以看到函數(shù)成功改變了列表list1的值。傳遞參數(shù)時(shí),名字list_param引用了與名字list1相同的對(duì)象,這個(gè)對(duì)象是可變的,在函數(shù)中成功修改了對(duì)象的值。

首先,名字list_param與名字list1指向?qū)ο螅?/p>

共享可變對(duì)象

然后,通過(guò)名字list_param修改了對(duì)象的值:

修改共享對(duì)象值

最后,這個(gè)修改對(duì)名字list1可見。

3.6 綁定何時(shí)發(fā)生

總的來(lái)說(shuō),觸發(fā)名字對(duì)象綁定的行為有以下一些:

  • 賦值操作;a = 1

  • 函數(shù)定義;

      def test():
          pass
    

將名字test綁定到函數(shù)對(duì)象

  • 類定義:

      class Test(object):
          pass
    

將名字Test綁定到類對(duì)象

  • 函數(shù)傳參;

      def test(i):
          pass
      test(1)
    

將名字i綁定到整數(shù)對(duì)象1

  • import語(yǔ)句:

     import sys
    

將名字sys綁定到指定模塊對(duì)象。

  • for循環(huán)

      for i in range(10):
          pass
    

每次循環(huán)都會(huì)綁定/重綁定名字i

  • as操作符

      with open('dir', 'r') as f:
          pass
      
      try:
          pass
      except NameError as ne:
          pass
    

with open語(yǔ)句,異常捕獲語(yǔ)句中的as都會(huì)發(fā)生名字的綁定

4、其他說(shuō)明

待續(xù)。。。

參考

  1. The Python Language References#Data model# Objects, values, types
  2. Python的名字綁定
  3. Python一切皆對(duì)象
  4. Code Like a Pythonista: Idiomatic Python
  5. python基礎(chǔ)(5):深入理解 python 中的賦值、引用、拷貝、作用域

腳注

<span id="jump">[1]</span> 在特定的控制條件下,改變對(duì)象的類型是可能的。但不是一種明智的做法,如果處理不當(dāng)?shù)脑?,?huì)發(fā)生一些奇怪的行為。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,799評(píng)論 1 118
  • 定義類并創(chuàng)建實(shí)例 在Python中,類通過(guò) class 關(guān)鍵字定義。以 Person 為例,定義一個(gè)Person類...
    績(jī)重KF閱讀 4,100評(píng)論 0 13
  • 個(gè)人筆記,方便自己查閱使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik閱讀 67,947評(píng)論 0 5
  • 這是一款全鍵盤手機(jī),觸屏智能機(jī)時(shí)代很是罕見。錫色的鍵盤漆已經(jīng)掉了不少,露出臟兮兮的塑料色,屏幕也多了好幾道劃痕——...
    劍白閱讀 10,564評(píng)論 5 16
  • 淡淡月光映出眼眶 時(shí)光慢慢無(wú)奈流淌 一人孤單單心房 是什么模樣。 云笛早已漫過(guò)街巷 心傷也已渲染淚光 一人喧鬧鬧心...
    瘋左閱讀 232評(píng)論 1 2

友情鏈接更多精彩內(nèi)容