跳轉到內容

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,那麼為什麼它不在成員字典中?但是,如果你還記得,我們是在類定義 Foo 中,而不是在 g 中新增 y。

>>> 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)。

多重繼承

[編輯 | 編輯原始碼]

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

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的函式新增到例項中。

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