Python中的類(classes)


Python的類機制使用盡可能少的新語法和語義將類引入語言。python的類提供了面向對象程序設計語言所有的 標准特性:類繼承機制允許有多個基類,一個派生類可以覆蓋基類中的任何方法,一個方法可以使用相同的名字調用 基類中的方法。

1 名字和對象

對象有其特性,同一個對象可以有多個名字,這與其它語言中的別名很相似。別名有時候像指針,例如將對象當做 函數參數傳遞的時候非常高效,因為只傳遞了指針,這避免了pascal中的兩套參數傳遞機制。

2 Python的域(scopes)和名稱空間(namespaces)

在引入類之前,我們講Python的域規則。類的定義巧妙地運用了名稱空間,所以你需要知道域和名稱空間如何工作才能理解發生了什么。

 首先從定義開始。 名稱空間是名字和對象之間的映射。多數名稱空間使用Python的字典來實現,但除非出於性能考慮,我們通常不關心具體如何實現。名稱空間的例子有,內置的名稱例如abs(),內置的異常名,模塊的全局名稱,函數調用時的局部名稱。在某種程度上,對象的屬性也構成名稱空間。

關於名稱空間最重要的一點是:不同名稱空間中的名稱沒有關系。例如 兩個不同模塊中都可以包含名為maximize的函數,這不會造成混餚,因為使用這些模塊時必須加上模塊名作為前綴。 另外,我把任何點后的名稱叫做屬性。例如,在表達式z.real中,real是對象z的屬性。嚴格來說,引用模塊中的名稱是對屬性的引用,在表達式modname.funcname中,modname是一個模塊,funcname是它的一個屬性。這個例子中模塊屬性和模塊 內定義的全局名稱有着直接的映射,它們有着相同的名稱空間。 屬性可能是只讀的或者可寫的,上面的例子中,屬性就是可寫的,例如:modname.the_ answer = 42.可寫的屬性可以被刪除, 例如 del modname.the_ answer 會刪除模塊 modname中的 the_ answer屬性。 

名稱空間在不同的時刻創建,有着不同的生命周期。包含內置名稱的名稱空間在Python解釋器啟動時被創建,且不會被刪除。 模塊的全局名稱空間在模塊被導入時被創建,正常情況下,模塊的名稱空間會持續到解釋器退出。來自腳本文件或者交互式環境 被解釋器最頂層調用執行的語句,被認為是 __ main __ 模塊的一部分,所以他們有着自己的全局名稱空間。 函數的局部名稱空間當函數被調用時被創建,函數返回時或者出現異常而函數又沒有提供處理方式時被刪除。當然,在遞歸調用 中每一次調用都有他們自己的局部名稱空間。 域(scpoe)是Python程序的一個名稱空間可以直接訪問的一個文本范圍,“直接訪問”在這里的意思時當對一個名字的訪問沒有 前綴時,會嘗試在名稱空間內查找這個名字。 在執行的任意時刻,至少有三個嵌套域,它們有名稱空間可以直接訪問。

  • 最內層的域,它會首先被搜索,包含局部名稱
  • 任何封裝函數的域,從最近的封裝域開始搜索,包含非局部,非全局的名稱
  • 倒數第二個域,包含當前模塊的全局名稱
  • 最外層的域,最后被搜索,包含內置名字的名稱空間

如果一個名字被聲名為全局的,那么所有的引用和賦值都是針對中間層的域,這一層域包含模塊的全局名稱。 意識到域是由文本決定是非常重要的,定義在模塊中的一個函數的全局域就是這個模塊的名稱空間,無論這個函數在哪兒, 通過哪個別名被調用。

3 初識類

 

3.1 定義類

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

類定義,像函數定義一樣,在執行時才會起作用。你可以把類定義放在任何地方比如if語句的分支,或者在函數內部。 在實際應用時,定義在類中的語句通常都是函數定義,但是其它語句也是允許出現的,並且有的時候非常有用。 當進入一個類定義時,一個新的名稱空間被創建,並且被當作局部域來使用。

3.2 類對象

類對象提供兩種操作,屬性引用和實例化。 屬性引用使用標准句法:obj.name. 有效的屬性名是類對象創建時類的名稱空間內的所有名字。 例如下面的類定義中,MyClass.i和MyClass.f都是有效的屬性名。

>>> class MyClass:
...     """A simple example class"""
...     i = 123
...     def f(self):
...         return 'hello world'
... 
>>> MyClass.i
123
>>> MyClass.i = 10

類的實例化使用函數記號,例如:

>>> x = MyClass()
>>> x.i
10

這個實例化操作創建了一個空對象,許多類在實例化時定義了一些初始化操作。例如:

>>> class MyClass():
...     def __init__(self):
...          self.data = []

當一個類定義了__ init __ 方法后,類實例化時會自動調用 __ init __ ().

__ init __ 函數還可以有其它參數,例如:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

3.3 實例化對象

現在我們可以用實例化的對象做些什么呢?它唯一可以進行的操作是屬性引用。有兩類有效的屬性名,數據屬性和方法。 數據屬性(data attributes)對應c++中的數據成員,數據屬性無需聲明,第一次給它賦值時就表明了它的存在。 另一種實例化的屬性引用叫做方法(Method).方法是對象內的一個函數。

3.4 方法對象

通常我們調用一個方法的方式是:

x.f()

但是,由於x.f是一個方法對象,所以它可以存儲起來,以便以后調用

>>> class MyClass:
...     """A simple example class"""
...     i = 12345
...     def f(self):
...         return 'hello world'
... 
>>> x = MyClass()
>>> x.f()
'hello world'
>>> xf = x.f
>>> xf()
'hello world'

你可能已經發現,f名名有一個參數,但是調用時為什么沒有使用呢。其實,原因在於 x.f() 與 MyClass.f(x) 是等價的。

>>> MyClass.f(x)
'hello world'

4 漫談

數據屬性如果和方法屬性名稱相同,前者會覆蓋后者。所以為了避免名稱沖突,最好養成一些習慣,比如方法名稱大寫,數據屬性 名稱前加一個短小,唯一的前綴。或者數據屬性用名詞,方法屬性用動詞。 數據屬性可以被方法引用,也可以被對象的使用者引用。換句話說,類不能實現為純抽象數據類型。 通常,方法的第一個參數是self.雖然這只是一個習慣用法,但是遵循一些習慣用法會讓你的程序可讀性更強。 函數定義沒有必要非在類里面,例如:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'hello world'
    h = g

一個方法可以通過self參數調用其它方法,

>>> class Bag:
...     def __init__(self):
...          self.data = []
...     def add(self, x):
...          self.data.append(x)
...     def addtwice(self, x):
...          self.add(x)
...          self.add(x)
... 
>>> b = Bag()
>>> b.data
[]
>>> b.add('1')
>>> b.data
['1']
>>> b.addtwice('x')
>>> b.data
['1', 'x', 'x']

5 派生

派生類的形式如下:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

BaseClassName必須在包含派生類的域內定義,BaseClassName可以是一個表達式,例如:

class DerivedClassName(modname.BaseClassName):

當派生類的對象引用了一個屬性時,會先在派生類內查找這個屬性名,如果找不到,再到基類中查找。 派生類可以覆蓋基類中的方法,即使基類中的方法被覆蓋了,也可以使用下面的方法來調用

BaseClassName.methodname(self, arguments)

6 多重繼承

Python 支持有限的多重繼承:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

在舊風格的類中,唯一的規則是深度優先,從左到右。以上述類定義為例,如果一個屬性沒有在 DerivedClassName中被 找到,那么會繼續搜索Base1,Base2等等 在新風格的類中,對方法的解析次序是動態改變的,這是因為類的繼承關系會呈現出一個或多個菱形。例如新風格的類都由 object類派生出,這樣就會就多條路徑通向object。為了避免基類被多次搜索,使用了線性化算法將所有基類排列成從左 到右的順序

7 私有變量和類局部引用

實例的私有變量只能在對象內部使用,python中常常使用例如 _ spam 的形式來代表API的非公有部分,無論是函數,方法還是 數據成員。類私有成員的特性的一種有效的用法是可以避免與子類中定義的名字沖突,這種機制叫做 mangling:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update # private copy of original update() method


class MappingSubclass(Mapping):
    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

注意上述代碼中 __ update 的使用,避免了子類中對update的覆蓋影響到基類 __ init__ 中的 update.

8 結構體

有時候我們可能需要像C中的struct那樣的數據類型,把少量的數據項放在一起。Python中可以使用定義一個空類來實現這一點:

# filename:p.py
class Employee:
    pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
>>> import p
>>> p.john
<p.Employee instance at 0xb71f50ac>
>>> p.john.name
'John Doe'
>>> p.john.dept
'computer lab'
>>> p.john.salary
1000

9 異常(Exceptions)也是類

用戶定義的異常也可以用類來表示,使用這種機制可以創建出可擴展,層次化的異常。 raise 語句有兩種新的形式

raise Class, instance
raise instance

第一種形式中,instance必須是Class的一個實例,或者是由它派生出的類。 第二種形式是下面這種形式的縮寫

raise instance.__class__, instance

下面這個例子會依次打印出B,C,D

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B,C,D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"
>>> import f
B
C
D

注意如果 B寫在最前面,會打印出BBB,這是因為raise C和raise D時,執行到except B是都會 print "B". 因為B是C,D的基類.

10 迭代器

現在你可能已經注意到了多數容器對象都可以使用for語句來循環

>>> for elem in [1,2,3]:
...     print elem
... 
1
2
3
>>> for elem in (1,2,3):
...     print elem
... 
1
2
3

這一風格清晰,簡捷,方便。迭代器的使用在Python中非常普便。for語句的背后,其實是對容器對象調用 iter(). 這個函數返回一個迭代器對象,它定義了next()函數,每次訪問容器中的一個元素。當沒有元素的時候,next()返回一個 StopIteration異常,告訴for語句循環結束了。

>>> s = 'asdf'
>>> it = iter(s)
>>> it
<iterator object at 0xb71f590c>
>>> it.next()
'a'
>>> it.next()
's'
>>> it.next()
'd'
>>> it.next()
'f'
>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

理解了迭代機制,就可以很容易地把迭代器加入你的類中,定義__ iter__ ()方法,返回一個有next()方法的對象。 如果一個類定義了next()函數,__ iter__ () 可以僅返回 self:

# q.py
class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index -1
        return self.data[self.index]

>>> import q
>>> rev = q.Reverse('spam')
>>> iter(rev)
<q.Reverse instance at 0xb71f588c>
>>> for char in rev:
...     print char
... 
m
a
p
s

11 生成器(Generators)

生成器是創建迭代器的一個簡單而強大的工具。它們像正常函數一樣,只是需要返回數據時使用 yield語句。

# d.py
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> import d
>>> for char in d.reverse('golf'):
...     print char
... 
f
l
o
g

任何可以使用生成器做的事,都可以使用前一版本的reverse實現,生成器之所以實現緊湊是因為自動創建了 __ iter() 和 next() 方法。

 

原文鏈接:http://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM