Python對象,名字以及綁定

大家讲道理
發布: 2017-05-28 10:00:57
原創
1465 人瀏覽過

Python進階- 物件,名字以及綁定

#寫在前面

如非特別說明,下文均基於Python3

1、一切對象

Python哲學:

Python中一切皆物件

1.1 資料模型-對象,值以及型別

物件是Python對資料的抽象。 Python程式中所有的資料都是物件或物件之間的關係表示的。 (在某種意義上,為順應馮·諾依曼「儲存式計算機」的模型,Python中的程式碼也是物件。)

Python中每一個物件都有一個身分標識,一個值以及一個類型。物件創建後,其身分標識絕對不會改變;可以把身分識別當作物件在記憶體中的位址。 is運算子比較兩個物件的身份識別;id()函數傳回表示物件身分識別的整數

CPython實作細節:CPython解釋器的實作中,id(x)函數傳回儲存x的記憶體位址

物件的型別決定了物件支援的運算(例如,物件有長度麼?),同時也決定了該型別物件可能的值。 type()函數傳回物件的型別(這個型別本身也是一個物件)。與其身分識別一樣,物件的型別也是不可改變的[1]

一些物件的值可以改變。可改變值的物件也稱為可變的(mutable);一旦創建,值恆定的物件也叫做不可變的(immutable)。 (當不可變容器物件中包含對可變物件的引用時,可變物件值改變時,這個不可變容器物件值也被改變了;然而,不可變容器物件仍被認為是不可變的,因為物件所包含的值集合確實是不可改變的。中包含了對其他物件的引用;那麼這些引用可以看做地址,即使地址指向的內容改變了,集合中的地址本身是沒有改變的。 所以不可變容器對象還是不可變對象。取決於其類型;例如,數字,字串和元組是不可變的,但字典和列表是可變的。

物件從不明確銷毀;當物件不可達時會被垃圾回收(譯註:物件沒有引用了)。解釋器實作允許垃圾回收延時或直接忽略——這取決於垃圾回收是如何實現的,只要沒有可達物件被回收。

CPython實作細節: CPython解釋器實作使用引用計數模式延時探測循環連結垃圾,這種方式可回收大多數不可達對象,但並不能保證循環引用的垃圾會被回收。查看gc模組的文件以了解控制循環垃圾回收的更多資訊。其他解釋器實作與CPython不同,CPython實作將來也許會改變。因此不能依賴垃圾回收器來回收不可達物件(因此應該總是明確關閉檔案物件。)。

需要注意,使用工具的調試追蹤功能可能會導致應該被回收的物件一直存活,使用try...#except語句捕獲異常也可以保持物件的存活。

有些物件引用瞭如檔案或視窗的外部資源。不言而喻持有資源的物件被垃圾回收後,資源也會被釋放,但因為沒有機制保證垃圾回收一定會發生,這些資源持有對像也提供了顯式釋放外部資源的方式,通常使用close()方法。強烈建議在程序中明確釋放資源。 try...<a href="http://www.php.cn/wiki/207.html" target="_blank">final</a>ly語句和with語句為釋放資源提供了便利。

有些物件包含對其他物件的引用,這些物件被稱為 容器。元組,列表和字典都是容器。引用的容器值的一部分。大多數情況下,當談論容器的值時,我們暗指容器包含的物件值集合,而不是物件的身份識別集合;然而,當談論容器的可變性時,我們暗指容器包含的物件的身份識別。因此,如果不可變物件(如元組)包含可變物件的引用,則可變物件改變時,其值也改變了。

類型影響物件的絕大多數行為。在某些情況下甚至對象的身份標識的重要性也受到影響:對於不可變類型,計算新值的操作實際上可能會返回已存在的,值和類型一樣的對象的引用,然而對於可變對象來說這是不可能的。例如,語句a = 1; b = 1執行之後,ab可能會也可能不會引用具有相同值得同一個對象,這取決於解釋器實作。但是語句c = []; d = []執行之後,可以保證cd會指向不同的,唯一的新建立的空列表。 (注意c = d = []分配相同的物件給cd)

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

1.2 物件小結

官方文件已經對Python 物件做了詳細的描述,這裡總結一下。

物件的三個特性:

  • 身份識別
    唯一識別物件;不可變;CPython#解釋器實作為物件的記憶體位址。
    操作:id(),內建函數id()函數傳回標識物件的一個整數;is比較兩個物件的身份識別。
    範例:

    >>> id(1)
    1470514832
    >>> 1 is 1
    True
    登入後複製
  • 類型
    決定物件支援的操作,可能的值;不可變。
    操作:type(),內建函數傳回物件的類型
    範例:

    >>> type('a')
    <class></class>
    登入後複製

  • 數據,可變/不可變
    操作:==運算子用於比較兩個物件的值是否相等,其他比較運算子比較物件間大小情況。
    範例:

    >>> 'python'
    'python'
    >>> 1 == 2
    False
    登入後複製

可變與不可變:一般認為,值不可變的物件是不可變對象,值可變的物件是可變對象,但是要注意不可變集合對象包含可變對象引用成員的情況。

Python中的物件:


#
# -*- coding: utf-8 -*-# filename: hello.py&#39;a test module&#39;author = &#39;Richard Cheng&#39;import sysclass Person(object):    &#39;&#39;&#39; Person class&#39;&#39;&#39;

    def init(self, name, age):        self.name = name        self.age = agedef tset():    print(sys.path)
    p = Person(&#39;Richard&#39;, 20)    print(p.name, &#39;:&#39;, p.age)def main():
    tset()if name == &#39;main&#39;:
    main()
登入後複製


##這段

Python程式碼中有很多對象,包括hello這個模組對象,創建的Person類別對象,幾個函數如test, main函數對象,數字,字串,甚至程式碼本身也是對象。

2、名字即「

變數

幾乎所有語言中都有「變數」的說法,嚴格說來,

Python#中的變數不應該叫變量,稱為名字更貼切。

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

2.1 其他語言有變數
其他語言中,為變數分配值就像將值放到「盒子」裡。


int a = 1;

Python對象,名字以及綁定

a現在有了一個整數1

為同一個變數分配值替換掉盒子的內容:


a =2;

Python對象,名字以及綁定

現在盒子

a中放了整數2

將一個變數分配給另一個變量,拷貝變數的值,並把它放到新的盒子裡:


int b = a;

Python對象,名字以及綁定

Python對象,名字以及綁定

#b是第二個盒子,裝有整數2的拷貝。盒子a有一份單獨的拷貝。

2.2 Python有名字

Python中,名字或識別碼就像將一個標籤捆綁到物件上一樣。
a = 1

Python對象,名字以及綁定#

这里,整数对象1有一个叫做a的标签。

如果重新给a分配值,只是简单的将标签移动到另一个对象:
a = 2

Python對象,名字以及綁定
Python對象,名字以及綁定

现在名字a贴到了整数对象2上面。原来的整数对象1不再拥有标签a,或许它还存在,但是不能通过标签a访问它了(当对象没有任何引用时,会被回收。)

如果将一个名字分配给另一名字,只是将另一个名字标签捆绑到存在的对象上:
b = a

Python對象,名字以及綁定

名字b只是绑定到与a引用的相同对象上的第二个标签而已。

虽然在Python中普遍使用“变量”(因为“变量”是普遍术语),真正的意思是名字或者标识符。Python中的变量是值得标签,不是装值得盒子。

2.3 指针?引用?名字?

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

2.1节中其他语言的例子,也只是针对于它们的基本类型而言的,若是指针或者引用,表现也跟Python的名字一样。这也在一定程度上说明了Python面向对象贯彻得更加彻底。

2.4 名字支持的操作

可以对一个变量做什么?声明变量,使用变量,修改变量的值。名字作为Python中的一个重要概念,可以对它做的操作有:

  • 定义;名字需要先定义才能使用,与变量需要先声明一样。

  • 绑定:名字的单独存在没有意义,必须将它绑定到一个对象上。

  • Python對象,名字以及綁定:名字可以重新引用另一个对象,这个操作就是Python對象,名字以及綁定。

  • 引用:为什么要定义名字,目的是使用它。

3、绑定的艺术

名字以及对象,它们之间必然会发生些什么。

3.1 变量的声明

其他如C/C++Java的高级语言,变量在使用前需要声明,或者说定义。以下在Java中声明变量:


public static void main(String[] args) {        int i = 0; // 先声明,后使用
        System.out.println(i); // 使用变量i}
登入後複製


这样,在可以访问到变量i所在作用域的地方,既可以使用i了。还有其他声明变量的方法么?好像没有了。

3.2 名字的定义

Python中有多种定义名字的途径,如函数定义,函数名就是引用函数对象的名字;类定义,类名就是指向类对象的名字,模块定义,模块名就是引用模块对象的名字;当然,最直观的还是赋值语句。

赋值语句

官方对赋值语句做了这样的说明(地址):

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

即:

赋值语句被用来将名字绑定或者Python對象,名字以及綁定给值,也用来修改可变对象的属性或项

那么,我们关心的,就是赋值语句将名字和值(对象)绑定起来了。

看一个简单的赋值语句:


a = 9
登入後複製


Python在处理这条语句时:

  1. 首先在内存中创建一个对象,表示整数9:

Python對象,名字以及綁定

  1. 然后创建名字a,并把它指向上述对象:

Python對象,名字以及綁定

上述过程就是通过赋值语句的名字对象绑定了。名字首次和对象绑定后,这个名字就定义在当前命名空间了,以后,在能访问到这个命名空间的作用域中可以引用该名字了。

3.3 引用不可变对象

定义完名字之后,就可以使用名字了,名字的使用称为“引用名字”。当名字指向可变对象和不可变对象时,使用名字会有不同的表现。


a = 9       #1a = a + 1   #2
登入後複製


语句1执行完后,名字a指向表示整数9的对象:

Python對象,名字以及綁定

由于整数是不可变对象,所以在语句2处引用名字a,试图将表示整数9的对象 + 1,但该对象的值是无法改变的。因此就将该对象表示的整数值91,以整数10新建一个整数对象:

Python對象,名字以及綁定

接下来,将名字a Python對象,名字以及綁定 到新建对象上,并移除名字对原对象的引用:

Python對象,名字以及綁定

使用id()函数,可以看到名字a指向的对象地址确实发生了改变:


>>> a = 9>>> id(a)1470514960>>> a = a + 1>>> id(a)1470514976
登入後複製


3.4 引用可变对象
3.4.1 示例1:改变可变对象的值

可变对象可以改变其值,并且不会造成地址的改变:


>>> list1 = [1]>>> id(list1)42695136>>> list1.append(2)>>> id(list1)42695136>>> list1
[1, 2]>>>
登入後複製


执行语句list1 = [1],创建一个list对象,并且其值集中添加1,将名字list1指向该对象:

Python對象,名字以及綁定

执行语句list1.append(2),由于list是可变对象,可以直接在其值集中添加2

Python對象,名字以及綁定

值得改变并没有造成list1引用的对象地址的改变。

3.4.2 示例2:可变对象Python對象,名字以及綁定

再来看一个比较“奇怪”的例子:


values = [1, 2, 3]
values[1] = valuesprint(values)
登入後複製


一眼望去,期待的结果应该是


[1, [1, 2, 3], 3]
登入後複製


但实际上结果是:


[1, [...], 3]
登入後複製


我们知道list中的元素可以是各种类型的,list类型是可以的:

Python對象,名字以及綁定

3.4.3 示例3:Python對象,名字以及綁定可变对象

观察以下代码段:


>>> list1 = [1]>>> id(list1)42695136>>> list1 = [1, 2]>>> id(list1)42717432
登入後複製


两次输出的名字list1引用对象的地址不一样,这是因为第二次语句list 1 = [1, 2] 对名字做了Python對象,名字以及綁定:

Python對象,名字以及綁定

3.5 共享对象

当两个或两个以上的名字引用同一个对象时,我们称这些名字共享对象。共享的对象可变性不同时,表现会出现差异。

3.5.1 共享不可变对象

函数attempt_change_immutable将参数i的值修改为2


def attempt_change_immutable(i):
    i = 2i = 1print(i)
attempt_change_immutable(i)print(i)
登入後複製


Output:


11
登入後複製


如果你对输出不感到意外,说明不是新手了 ^_^。

  1. 首先,函数的参数i与全局名字i不是在同一命名空间中,所以它们之间不相互影响。

  2. 调用函数时,将两个名字i都指向了同一个整数对象。

  3. 函数中修改i的值为2, 因为整数对象不可变,所以新建值为2的整数对象,并把函数中的名字i绑定到对象上。

  4. 全局名字i的绑定关系并没有被改变。

Python對象,名字以及綁定

Python對象,名字以及綁定

值得注意的是,这部分内容与命名空间和作用域有关系,另外有文章介绍它们,可以参考。

3.5.2 Python對象,名字以及綁定

函数attempt_change_mutable为列表增加字符串。


def attempt_change_mutable(list_param):
    list_param.append(&#39;test&#39;)

list1 = [1]print(list1)
attempt_change_mutable(list1)print(list1)
登入後複製


output:


[1]
[1, &#39;test&#39;]
登入後複製


可以看到函数成功改变了列表list1的值。传递参数时,名字list_param引用了与名字list1相同的对象,这个对象是可变的,在函数中成功修改了对象的值。

首先,名字list_param与名字list1指向对象:

Python對象,名字以及綁定

然后,通过名字list_param修改了对象的值:

Python對象,名字以及綁定

最后,这个修改对名字list1可见。

3.6 绑定何时发生

总的来说,触发名字对象绑定的行为有以下一些:

  • 赋值操作;a = 1

  • 函数定义;

    def test():
        pass
    登入後複製

    将名字test绑定到函数对象

  • 类定义:

    class Test(object):
        pass
    登入後複製

    将名字Test绑定到类对象

  • 函数传参;

    def test(i):
        pass
    test(1)
    登入後複製

    将名字i绑定到整数对象1

  • import语句:

    import sys
    登入後複製

    将名字sys绑定到指定模块对象。

  • <a href="http://www.php.cn/wiki/125.html" target="_blank">for</a>循环

    for i in range(10):
        pass
    登入後複製

    每次循环都会绑定/Python對象,名字以及綁定名字i

  • as操作符

    with open(&#39;dir&#39;, &#39;r&#39;) as f:
        pass
    
    try:
        pass
    except NameError as ne:
        pass
    登入後複製

    with open语句,异常捕获语句中的as都会发生名字的绑定

    4、其他说明

    待续。。。

    参考

    1. The Python Language References#Data model# Objects, values, types

    2. Python的名字绑定

    3. Python一切皆对象

    4. Code Like a Pythonista: Idiomatic Python

    5. python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域

    脚注

    [1] 在特定的控制条件下,改变对象的类型是可能的。但不是一种明智的做法,如果处理不当的话,会发生一些奇怪的行为。

    以上是Python對象,名字以及綁定的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!