跳轉到內容

Python 程式設計/類

來自華夏公益教科書


類是一種將相似的資料和函式聚合在一起的方式。類本質上是一個作用域,在其中執行各種程式碼(尤其是函式定義),而此作用域的區域性變數成為類的屬性,以及由此類構建的任何物件的屬性。由類構建的物件稱為該類的例項

Python 中的類概覽

import math
class MyComplex:
  """A complex number"""       # Class documentation
  classvar = 0.0               # A class attribute, not an instance one
  def phase(self):             # A method
    return math.atan2(self.imaginary, self.real)
  def __init__(self):          # A constructor
    """A constructor"""
    self.real = 0.0            # An instance attribute
    self.imaginary = 0.0
c1 = MyComplex()
c1.real = 3.14                 # No access protection
c1.imaginary = 2.71
phase = c1.phase()             # Method call
c1.undeclared = 9.99           # Add an instance attribute
del c1.undeclared              # Delete an instance attribute

print(vars(c1))                # Attributes as a dictionary
vars(c1)["undeclared2"] = 7.77 # Write access to an attribute
print(c1.undeclared2)          # 7.77, indeed

MyComplex.classvar = 1         # Class attribute access
print(c1.classvar == 1)        # True; class attribute access, not an instance one
print("classvar" in vars(c1))  # False
c1.classvar = -1               # An instance attribute overshadowing the class one
MyComplex.classvar = 2         # Class attribute access
print(c1.classvar == -1)       # True; instance attribute access
print("classvar" in vars(c1))  # True

class MyComplex2(MyComplex):   # Class derivation or inheritance
  def __init__(self, re = 0, im = 0):
    self.real = re             # A constructor with multiple arguments with defaults
    self.imaginary = im
  def phase(self):
    print("Derived phase")
    return MyComplex.phase(self) # Call to a base class; "super"
c3 = MyComplex2()
c4 = MyComplex2(1, 1)
c4.phase()                     # Call to the method in the derived class

class Record: pass             # Class as a record/struct with arbitrary attributes
record = Record()
record.name = "Joe"
record.surname = "Hoe"

定義類

[編輯 | 編輯原始碼]

要定義類,請使用以下格式

class ClassName:
    "Here is an explanation about your class"
    pass

此類定義中的大寫字母是約定,但語言不需要。通常最好至少新增一個簡短的解釋來說明你的類應該做什麼。上面的程式碼中的 pass 語句只是告訴 Python 直譯器繼續執行,不做任何事情。一旦你添加了第一個語句,就可以刪除它。

例項構建

[編輯 | 編輯原始碼]

類是一個可呼叫物件,在被呼叫時構建類的例項。假設我們建立一個類 Foo。

class Foo:
    "Foo is our new toy."
    pass

要構建類 Foo 的例項,請“呼叫”類物件

f = Foo()

這會構建類 Foo 的一個例項,並在 f 中建立一個指向它的引用。

類成員

[編輯 | 編輯原始碼]

為了訪問類例項的成員,請使用語法 <類例項>.<成員>。還可以使用 <類名>.<成員> 訪問類定義的成員。

方法是類中的函式。第一個引數(方法必須始終至少接受一個引數)始終是呼叫函式的類例項。例如

>>> class Foo:
...     def setx(self, x):
...         self.x = x
...     def bar(self):
...         print(self.x)

如果執行此程式碼,不會發生任何事情,至少在構建 Foo 的例項之前,然後在該例項上呼叫 bar 之前,不會發生任何事情。

為什麼是必填引數?
[編輯 | 編輯原始碼]

在普通函式中,如果你要設定一個變數,例如 test = 23,你將無法訪問 test 變數。鍵入 test 會顯示它未定義。這在類函式中也是如此,除非它們使用 self 變數。

基本上,在前面的示例中,如果我們刪除 self.x,函式 bar 將無法執行任何操作,因為它無法訪問 x。setx() 中的 x 會消失。self 引數將變數儲存到類的“共享變數”資料庫中。

為什麼是 self?

[編輯 | 編輯原始碼]

你不需要使用 self。但是,使用 self 是一種規範。

呼叫方法

[編輯 | 編輯原始碼]

呼叫方法與呼叫函式非常相似,但不是將例項作為第一個引數傳遞,就像形式引數列表所建議的那樣,而是將函式作為例項的屬性使用。

>>> f = Foo()
>>> f.setx(5)
>>> f.bar()

這將輸出

5

可以使用它作為定義類的屬性(而不是該類的例項)來呼叫任意物件上的方法,如下所示

>>> Foo.setx(f,5)
>>> Foo.bar(f)

這將具有相同的輸出。

動態類結構

[編輯 | 編輯原始碼]

正如上面的方法 setx 所示,Python 類中的成員可以在執行時改變,而不僅僅是它們的值,這與 C++ 或 Java 等語言中的類不同。我們甚至可以在執行上面的程式碼後刪除 f.x。

>>> del f.x
>>> f.bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 5, in bar
AttributeError: Foo instance has no attribute 'x'

這帶來的另一個影響是,我們可以在程式執行期間更改 Foo 類的定義。在下面的程式碼中,我們在 Foo 類定義中建立一個名為 y 的成員。如果我們隨後建立 Foo 的新例項,它現在將擁有這個新成員。

>>> Foo.y = 10
>>> g = Foo()
>>> g.y
10

檢視類字典

[編輯 | 編輯原始碼]

這一切的核心是一個 字典,可以透過“vars(ClassName)”訪問

>>> vars(g)
{}

起初,這個輸出毫無意義。我們剛剛看到 g 擁有成員 y,那麼為什麼它不在成員字典中呢?但是,如果你還記得,我們將 y 放入類定義 Foo 中,而不是 g 中。

>>> vars(Foo)
{'y': 10, 'bar': <function bar at 0x4d6a3c>, '__module__': '__main__',
 'setx': <function setx at 0x4d6a04>, '__doc__': None}

在那裡我們擁有 Foo 類定義的所有成員。當 Python 檢查 g.member 時,它首先檢查 g 的 vars 字典中的“member”,然後檢查 Foo。如果我們建立 g 的新成員,它將被新增到 g 的字典中,但不會新增到 Foo 的字典中。

>>> g.setx(5)
>>> vars(g)
{'x': 5}

請注意,如果我們現在為 g.y 分配一個值,我們不是將該值分配給 Foo.y。Foo.y 將仍然是 10,但 g.y 現在將覆蓋 Foo.y

>>> g.y = 9
>>> vars(g)
{'y': 9, 'x': 5}
>>> vars(Foo)
{'y': 10, 'bar': <function bar at 0x4d6a3c>, '__module__': '__main__',
 'setx': <function setx at 0x4d6a04>, '__doc__': None}

果然,如果我們檢查這些值

>>> g.y
9
>>> Foo.y
10

請注意,f.y 也將是 10,因為 Python 在 vars(f) 中找不到“y”,所以它將從 vars(Foo) 中獲取“y”的值。

有些人可能還注意到,Foo 中的方法出現在類字典中,與 x 和 y 位於同一位置。如果你還記得關於 lambda 函式 的那一節,我們可以將函式視為變數。這意味著我們可以在執行時將方法分配給類,就像我們分配變數一樣。但是,如果你這樣做,請記住,如果我們呼叫類例項的方法,傳遞給方法的第一個引數始終是類例項本身。

更改類字典

[編輯 | 編輯原始碼]

我們也可以使用類的 __dict__ 成員訪問類的成員字典。

>>> g.__dict__
{'y': 9, 'x': 5}

如果我們在 g.__dict__ 中新增、刪除或更改鍵值對,這將與我們在 g 的成員中進行這些更改具有相同的效果。

>>> g.__dict__['z'] = -4
>>> g.z
-4

為什麼要使用類?

[編輯 | 編輯原始碼]

類之所以特殊,是因為一旦建立了例項,該例項就獨立於所有其他例項。我可以有兩個例項,每個例項都有不同的 x 值,它們不會影響彼此的 x。

f = Foo()
f.setx(324)
f.boo()
g = Foo()
g.setx(100)
g.boo()

f.boo()g.boo() 將列印不同的值。

新式類

[編輯 | 編輯原始碼]

新式類在 Python 2.2 中引入。新式類是基類為內建類的類,最常見的是 object。在低階層面上,舊類和新類之間的主要區別在於它們的型別。舊類例項都具有 instance 型別。新式類例項將返回與其型別相同的 x.__class__ 的內容。這將使用者定義的類與內建類放在同一個水平面上。舊/經典類將在 Python 3 中消失。考慮到這一點,所有開發都應該使用新式類。新式類還添加了像屬性和靜態方法這樣的結構,這些結構為 Java 程式設計師所熟知。

舊/經典類

>>> class ClassicFoo:
...     def __init__(self):
...         pass

新式類

>>> class NewStyleFoo(object):
...     def __init__(self):
...         pass

屬性是具有 getter 和 setter 方法的屬性。

>>> class SpamWithProperties(object):
...     def __init__(self):
...         self.__egg = "MyEgg"
...     def get_egg(self):
...         return self.__egg
...     def set_egg(self, egg):
...         self.__egg = egg
...     egg = property(get_egg, set_egg)

>>> sp = SpamWithProperties()
>>> sp.egg
'MyEgg'
>>> sp.egg = "Eggs With Spam"
>>> sp.egg
'Eggs With Spam'
>>>

並且從 Python 2.6 開始,使用 @property 裝飾器

>>> class SpamWithProperties(object):
...     def __init__(self):
...         self.__egg = "MyEgg"
...     @property
...     def egg(self):
...         return self.__egg
...     @egg.setter
...     def egg(self, egg):
...         self.__egg = egg

靜態方法

[編輯 | 編輯原始碼]

Python 中的靜態方法就像 C++ 或 Java 中的對應方法一樣。靜態方法沒有 "self" 引數,也不需要您在使用它們之前例項化類。它們可以使用 staticmethod() 定義。

>>> class StaticSpam(object):
...     def StaticNoSpam():
...         print("You can't have have the spam, spam, eggs and spam without any spam... that's disgusting")
...     NoSpam = staticmethod(StaticNoSpam)

>>> StaticSpam.NoSpam()
You can't have have the spam, spam, eggs and spam without any spam... that's disgusting

它們也可以使用函式裝飾器 @staticmethod 定義。

>>> class StaticSpam(object):
...     @staticmethod
...     def StaticNoSpam():
...         print("You can't have have the spam, spam, eggs and spam without any spam... that's disgusting")

像所有面向物件的語言一樣,Python 提供對繼承的支援。繼承是一個簡單的概念,透過它,一個類可以擴充套件另一個類的功能,或者在 Python 的情況下,擴充套件多個類的功能。使用以下格式:

class ClassName(BaseClass1, BaseClass2, BaseClass3,...):
    ...

ClassName 被稱為派生類,即從基類派生而來。派生類將具有其基類的所有成員。如果在派生類和基類中定義了方法,派生類中的成員將覆蓋基類中的成員。為了使用基類中定義的方法,有必要將該方法作為屬性在定義類上呼叫,如上面的 Foo.setx(f,5)

>>> class Foo:
...     def bar(self):
...         print("I'm doing Foo.bar()")
...     x = 10
...
>>> class Bar(Foo):
...     def bar(self):
...         print("I'm doing Bar.bar()")
...         Foo.bar(self)
...     y = 9
...
>>> g = Bar()
>>> Bar.bar(g)
I'm doing Bar.bar()
I'm doing Foo.bar()
>>> g.y
9
>>> g.x
10

再次,我們可以透過檢視類字典來了解發生了什麼。

>>> vars(g)
{}
>>> vars(Bar)
{'y': 9, '__module__': '__main__', 'bar': <function bar at 0x4d6a04>,
 '__doc__': None}
>>> vars(Foo)
{'x': 10, '__module__': '__main__', 'bar': <function bar at 0x4d6994>,
 '__doc__': None}

當我們呼叫 g.x 時,它首先按慣例檢視 vars(g) 字典。如上所述,它接下來會檢查 vars(Bar),因為 g 是 Bar 的一個例項。但是,由於繼承,如果它在 vars(Bar) 中沒有找到 x,Python 將會檢查 vars(Foo)。

多重繼承

[編輯 | 編輯原始碼]

如部分 #Inheritance 所示,一個類可以從多個類派生

class ClassName(BaseClass1, BaseClass2, BaseClass3):
    pass

多重繼承中一個棘手的問題是方法解析:在方法呼叫時,如果方法名來自多個基類或它們的基類,應該呼叫哪個基類方法。

方法解析順序取決於類是舊式類還是新式類。對於舊式類,派生類按從左到右的順序考慮,基類的基類在移動到右邊之前被考慮。因此,在上面,BaseClass1 首先被考慮,如果在那裡沒有找到方法,則考慮 BaseClass1 的基類。如果失敗,則考慮 BaseClass2,然後考慮它的基類,依此類推。對於新式類,請參閱線上的 Python 文件。

連結

特殊方法

[編輯 | 編輯原始碼]

有一些方法具有保留名稱,這些名稱用於特殊目的,例如模擬數值或容器操作,以及其他一些操作。所有這些名稱都以兩個下劃線開頭和結尾。約定是,以單個下劃線開頭的成員是它們引入的範圍的 "私有" 成員。

初始化和刪除

[編輯 | 編輯原始碼]

這些目的之一是構造例項,它的特殊名稱是 "__init__"。__init__() 在返回例項之前被呼叫(手動返回例項是不必要的)。例如:

class A:
    def __init__(self):
        print('A.__init__()')
a = A()

輸出

A.__init__()

__init__() 可以接收引數,在這種情況下,必須向類傳遞引數才能建立例項。例如:

class Foo:
    def __init__ (self, printme):
        print(printme)
foo = Foo('Hi!')

輸出

Hi!

以下示例展示了使用 __init__() 和不使用 __init__() 之間的區別

class Foo:
    def __init__ (self, x):
         print(x)
foo = Foo('Hi!')
class Foo2:
    def setx(self, x):
        print(x)
f = Foo2()
Foo2.setx(f,'Hi!')

輸出

Hi!
Hi!

類似地,當例項被銷燬時(例如,當它不再被引用時)會呼叫 "__del__"。

__enter__ 和 __exit__
[編輯 | 編輯原始碼]

這些方法也是建構函式和解構函式,但它們只在使用 with 例項化類時執行。示例

class ConstructorsDestructors:
    def __init__(self):
        print('init')

    def __del__(self):
        print('del')

    def __enter__(self):
        print('enter')

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit')

with ConstructorsDestructors():
    pass
init
enter
exit
del

元類 建構函式。

將物件轉換為字串,例如使用 print 語句或使用 str() 轉換函式,可以透過覆蓋 __str__ 來覆蓋。通常,__str__ 返回物件內容的格式化版本。這通常不會是可執行的內容。

例如

class Bar:
    def __init__ (self, iamthis):
        self.iamthis = iamthis
    def __str__ (self):
        return self.iamthis
bar = Bar('apple')
print(bar)

輸出

apple

這個函式很像 __str__()。如果 __str__ 不存在,但這個存在,則使用這個函式的輸出來代替列印。__repr__ 用於以字串形式返回物件的表示。一般來說,它可以執行以獲取回原始物件。

例如

class Bar:
    def __init__ (self, iamthis):
        self.iamthis = iamthis
    def __repr__(self):
        return "Bar('%s')" % self.iamthis
bar = Bar('apple')
bar

輸出(注意區別:可能不需要將它放在 print 中,但在 Python 2.7 中需要)

Bar('apple')
字串表示覆蓋函式
函式 運算子
__str__ str(A)
__repr__ repr(A)
__unicode__ unicode(x) (僅限 2.x)
__setattr__
[編輯 | 編輯原始碼]

這是負責設定類屬性的函式。它提供了正在分配的變數的名稱和值。當然,每個類都帶有一個預設的 __setattr__,它只是設定變數的值,但我們可以覆蓋它。

>>> class Unchangable:
...    def __setattr__(self, name, value):
...        print("Nice try")
...
>>> u = Unchangable()
>>> u.x = 9
Nice try
>>> u.x
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: Unchangable instance has no attribute 'x'
__getattr___
[編輯 | 編輯原始碼]

與 __setattr__ 相似,除了這個函式是在我們嘗試訪問類成員時被呼叫,預設情況下它只返回該值。

>>> class HiddenMembers:
...     def __getattr__(self, name):
...         return "You don't get to see " + name
...
>>> h = HiddenMembers()
>>> h.anything
"You don't get to see anything"
__delattr__
[編輯 | 編輯原始碼]

這個函式被呼叫來刪除屬性。

>>> class Permanent:
...     def __delattr__(self, name):
...         print(name, "cannot be deleted")
...
>>> p = Permanent()
>>> p.x = 9
>>> del p.x
x cannot be deleted
>>> p.x
9
屬性覆蓋函式
函式 間接形式 直接形式
__getattr__ getattr(A, B) A.B
__setattr__ setattr(A, B, C) A.B = C
__delattr__ delattr(A, B) del A.B

運算子過載

[編輯 | 編輯原始碼]

運算子過載允許我們使用內建的 Python 語法和運算子來呼叫我們定義的函式。

二元運算子
[編輯 | 編輯原始碼]

如果一個類具有 __add__ 函式,我們可以使用 '+' 運算子來新增類的例項。這將呼叫 __add__ 函式,並將類的兩個例項作為引數傳遞給它,返回值將是加法的結果。

>>> class FakeNumber:
...     n = 5
...     def __add__(A,B):
...         return A.n + B.n
...
>>> c = FakeNumber()
>>> d = FakeNumber()
>>> d.n = 7
>>> c + d
12

要覆蓋增強賦值運算子,只需在正常的二元運算子前面新增 'i' 即可,例如,對於 '+=',使用 '__iadd__' 代替 '__add__'。該函式將被賦予一個引數,該引數將是增強賦值運算子右側的物件。該函式的返回值將被賦值給運算子左側的物件。

>>> c.__imul__ = lambda B: B.n - 6
>>> c *= d
>>> c
1

需要注意的是,如果增強運算子函式沒有被直接設定,增強賦值運算子也會使用正常的運算子函式。這將按預期工作,"__add__" 將被呼叫用於 "+=" 等等。

>>> c = FakeNumber()
>>> c += d
>>> c
12
二元運算子過載函式
函式 運算子
__add__ A + B
__sub__ A - B
__mul__ A * B
__truediv__ A / B
__floordiv__ A // B
__mod__ A % B
__pow__ A ** B
__and__ A & B
__or__ A | B
__xor__ A ^ B
__eq__ A == B
__ne__ A != B
__gt__ A > B
__lt__ A < B
__ge__ A >= B
__le__ A <= B
__lshift__ A << B
__rshift__ A >> B
__contains__ A in B
A not in B
一元運算子
[編輯 | 編輯原始碼]

一元運算子將簡單地傳遞它們被呼叫的類的例項。

>>> FakeNumber.__neg__ = lambda A : A.n + 6
>>> -d
13
一元運算子過載函式
函式 運算子
__pos__ +A
__neg__ -A
__inv__ ~A
__abs__ abs(A)
__len__ len(A)
專案運算子
[編輯 | 編輯原始碼]

在 Python 中,還可以覆蓋索引和切片運算子。這讓我們可以使用 class[i] 和 class[a:b] 語法在自己的物件上。

專案運算子最簡單的形式是 __getitem__。它將類的例項和索引的值作為引數。

>>> class FakeList:
...     def __getitem__(self,index):
...         return index * 2
...
>>> f = FakeList()
>>> f['a']
'aa'

我們還可以為與將值賦值給專案相關的語法定義一個函式。除了 __getitem__ 的引數外,該函式的引數還包括要賦值的值。

>>> class FakeList:
...     def __setitem__(self,index,value):
...         self.string = index + " is now " + value
...
>>> f = FakeList()
>>> f['a'] = 'gone'
>>> f.string
'a is now gone'

我們也可以對切片做同樣的事情。同樣,每個語法都有不同的引數列表與之關聯。

>>> class FakeList:
...     def __getslice___(self,start,end):
...         return str(start) + " to " + str(end)
...
>>> f = FakeList()
>>> f[1:4]
'1 to 4'

請記住,切片語法中的起始和結束引數可以為空。在這種情況下,Python 對起始和結束都有預設值,如下所示。

>> f[:]
'0 to 2147483647'

請注意,此處顯示的切片結束的預設值只是 32 位系統上的最大可能的有符號整數,並且可能根據您的系統和 C 編譯器而有所不同。

  • __setslice__ 的引數是 (self,start,end,value)

我們還有用於刪除專案和切片的運算子。

  • __delitem__ 的引數是 (self,index)
  • __delslice__ 的引數是 (self,start,end)

請注意,這些與 __getitem__ 和 __getslice__ 相同。

專案運算子過載函式
函式 運算子
__getitem__ C[i]
__setitem__ C[i] = v
__delitem__ del C[i]
__getslice__ C[s:e]
__setslice__ C[s:e] = v
__delslice__ del C[s:e]

其他過載

[編輯 | 編輯原始碼]
其他過載函式
函式 運算子
__cmp__ cmp(x, y)
__hash__ hash(x)
__nonzero__ bool(x)
__call__ f(x)
__iter__ iter(x)
__reversed__ reversed(x) (2.6+)
__divmod__ divmod(x, y)
__int__ int(x)
__long__ long(x)
__float__ float(x)
__complex__ complex(x)
__hex__ hex(x)
__oct__ oct(x)
__index__
__copy__ copy.copy(x)
__deepcopy__ copy.deepcopy(x)
__sizeof__ sys.getsizeof(x) (2.6+)
__trunc__ math.trunc(x) (2.6+)
__format__ format(x, ...) (2.6+)

程式設計實踐

[編輯 | 編輯原始碼]

Python 類的靈活性意味著類可以採用各種行為。但是,為了易於理解,最好謹慎使用 Python 的許多工具。嘗試在類定義中宣告所有方法,並始終使用 <class>.<member> 語法,而不是 __dict__,只要有可能。檢視C++Java 中的類,看看大多數程式設計師會從類中期待什麼。

由於 Python 類中所有 Python 成員都可以被類外部的函式/方法訪問,因此除了覆蓋 __getattr__、__setattr__ 和 __delattr__ 之外,沒有辦法強制封裝。但是,一般做法是,類的建立者或模組的建立者只需要相信使用者只會使用預期的介面,並且為了那些確實需要訪問該模組的使用者,避免限制對該模組工作機制的訪問。當使用類或模組的預期介面以外的部分時,請記住這些部分可能會在模組的後續版本中發生變化,您甚至可能會在模組中導致錯誤或未定義的行為,因為封裝是私有的。

文件字串

[編輯 | 編輯原始碼]

定義類時,習慣上使用類定義開頭的字串字面量來記錄類。然後,該字串將被放置在類定義的 __doc__ 屬性中。

>>> class Documented:
...     """This is a docstring"""
...     def explode(self):
...         """
...         This method is documented, too! The coder is really serious about
...         making this class usable by others who don't know the code as well
...         as he does.
...
...         """
...         print("boom")
>>> d = Documented()
>>> d.__doc__
'This is a docstring'

文件字串是一種非常有用的記錄程式碼的方法。即使您從未編寫過任何單獨的文件(承認吧,對於許多程式設計師來說,這樣做是優先順序最低的事情),在您的類中包含資訊豐富的文件字串將極大地提高它們的可用性。

存在一些將 Python 程式碼中的文件字串轉換為可讀 API 文件的工具,例如EpyDoc

也不要僅僅停留在記錄類定義上。類中的每個方法也應該有自己的文件字串。請注意,上述示例類 Documented 中的方法 explode 的文件字串有一個相當長的跨越多行的文件字串。它的格式符合 Python 建立者 Guido van Rossum 在PEP 8 中的風格建議。

在執行時新增方法

[編輯 | 編輯原始碼]
到一個類
[編輯 | 編輯原始碼]

在執行時向類新增方法非常容易。假設我們有一個名為Spam的類和一個函式cook。我們希望能夠在Spam類的所有例項上使用函式cook。

class Spam:
  def __init__(self):
    self.myeggs = 5

def cook(self):
  print("cooking %s eggs" % self.myeggs)

Spam.cook = cook   #add the function to the class Spam
eggs = Spam()      #NOW create a new instance of Spam
eggs.cook()        #and we are ready to cook!

這將輸出

cooking 5 eggs
到一個類的例項
[編輯 | 編輯原始碼]

向已經建立的類的例項新增方法有點棘手。再次假設我們有一個名為Spam的類,並且我們已經建立了eggs。但是我們發現我們想烹飪這些雞蛋,但我們不想建立一個新的例項,而是使用已經建立的那個。

class Spam:
  def __init__(self):
    self.myeggs = 5

eggs = Spam()

def cook(self):
  print("cooking %s eggs" % self.myeggs)

import types
f = types.MethodType(cook, eggs, Spam)
eggs.cook = f

eggs.cook()

現在我們可以烹飪我們的雞蛋,最後一條語句將輸出

cooking 5 eggs
使用一個函式
[編輯 | 編輯原始碼]

我們還可以編寫一個函式,使向類的例項新增方法的過程更容易。

def attach_method(fxn, instance, myclass):
  f = types.MethodType(fxn, instance, myclass)
  setattr(instance, fxn.__name__, f)

我們現在只需要呼叫attach_method,並將要附加的函式、要附加到的例項以及例項派生的類作為引數傳遞。因此我們的函式呼叫可能如下所示

attach_method(cook, eggs, Spam)

注意在函式add_method中我們不能寫instance.fxn = f因為這會將一個名為fxn的函式新增到例項中。

[編輯 | 編輯原始碼]
華夏公益教科書