Python類對象


python類對象

python類對象支持兩種操作:屬性引用和實例化。

屬性引用 使用 Python 中所有屬性引用所使用的標准語法: obj.name。 有效的屬性名稱是類對象被創建時存在於類命名空間中的所有名稱。 因此,如果類定義是這樣的:

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

 

那么 MyClass.i 和 MyClass.f 就是有效的屬性引用,將分別返回一個整數和一個函數對象。 類屬性也可以被賦值,因此可以通過賦值來更改 MyClass.i 的值。 __doc__ 也是一個有效的屬性,將返回所屬類的文檔字符串: "Asimple example class"

類的 實例化 是使用函數表示法。 可以相像類對象就是會返回一個新的類實例的不帶參數的函數。 舉例來說(假設使用上述的類):

x = MyClass()

 

創建類的新 實例 並將此對象分配給局部變量 x

實例化操作(“調用”類對象)會創建一個空對象。 許多類喜歡創建帶有特定初始狀態的自定義實例。 為此類定義可能包含一個名為 __init__() 的特殊方法,就像這樣:

def __init__(self):
    self.data = []

 

當一個類定義了 __init__() 方法時,類的實例化操作會自動為新創建的類實例發起調用 __init__()。 因此在這個示例中,可以通過以下語句獲得一個經初始化的新實例:

x = MyClass()

 

當然,__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)

 

實例對象

現在我們可以用實例對象做什么?實例對象理解的唯一操作是屬性引用。有兩種有效的屬性名稱,數據屬性和方法。

數據屬性 對應於 Smalltalk 中的“實例變量”,以及 C++ 中的“數據成員”。 數據屬性不需要聲明;像局部變量一樣,它們將在第一次被賦值時產生。 例如,如果 x 是上面創建的 MyClass 的實例,則以下代碼段將打印數值 16,且不保留任何追蹤信息:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter) del x.counter

 

另一類實例屬性引用稱為 方法。 方法是“從屬於”對象的函數。 (在 Python 中,方法這個術語並不是類實例所特有的:其他對方也可以有方法。 例如,列表對象具有 append, insert, remove, sort 等方法。 然而,在以下討論中,我們使用方法一詞將專指類實例對象的方法,除非另外顯式地說明。)

實例對象的有效方法名稱依賴於其所屬的類。 根據定義,一個類中所有是函數對象的屬性都是定義了其實例的相應方法。 因此在我們的示例中,x.f 是有效的方法引用,因為 MyClass.f 是一個python函數,而 x.i 不是方法,因為 MyClass.i 不是一個函數。 但是 x.f 與 MyClass.f 並不是一回事 --- 它是一個 方法對象,不是函數對象。

方法對象

通常,方法在綁定后立即被調用:

x.f()

 

在 MyClass 示例中,這將返回字符串 'hello world'。 但是,立即調用一個方法並不是必須的: x.f 是一個方法對象,它可以被保存起來以后再調用。 例如:

xf = x.f
while True: print(xf())

 

將繼續打印 hello world,直到結束。

當一個方法被調用時到底發生了什么? 你可能已經注意到上面調用 x.f() 時並沒有帶參數,雖然 f() 的函數定義指定了一個參數。 這個參數發生了什么事? 當不帶參數地調用一個需要參數的函數時 Python 肯定會引發異常 --- 即使參數實際未被使用...

實際上,你可能已經猜到了答案:方法的特殊之處就在於實例對象會作為函數的第一個參數被傳入。 在我們的示例中,調用 x.f() 其實就相當於 MyClass.f(x)。 總之,調用一個具有 n 個參數的方法就相當於調用再多一個參數的對應函數,這個參數值為方法所屬實例對象,位置在其他參數之前。

如果你仍然無法理解方法的運作原理,那么查看實現細節可能會澄清問題。 當一個實例的非數據屬性被引用時,將搜索實例所屬的類。 如果名稱表示一個屬於函數對象的有效類屬性,會通過合並打包(指向)實例對象和函數對象到一個抽象對象中的方式來創建一個方法對象:這個抽象對象就是方法對象。 當附帶參數列表調用方法對象時,將基於實例對象和參數列表構建一個新的參數列表,並使用這個新參數列表調用相應的函數對象。

類和實例變量

一般來說,實例變量用於每個實例的唯一數據,而類變量用於類的所有實例共享的屬性和方法:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name): self.name = name # instance variable unique to each instance >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # shared by all dogs 'canine' >>> e.kind # shared by all dogs 'canine' >>> d.name # unique to d 'Fido' >>> e.name # unique to e 'Buddy'

 

正如 名稱和對象 中已討論過的,共享數據可能在涉及 mutable 對象例如python列表python字典的時候導致令人驚訝的結果。 例如以下代碼中的 tricks 列表不應該被用作類變量,因為所有的 Dog 實例將只共享一個單獨的列表:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # unexpectedly shared by all dogs ['roll over', 'play dead']

 

正確的類設計應該使用實例變量:

class Dog:

    def __init__(self, name): self.name = name self.tricks = [] # creates a new empty list for each dog def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks ['roll over'] >>> e.tricks ['play dead']

 

補充說明

數據屬性會覆蓋掉具有相同名稱的方法屬性;為了避免會在大型程序中導致難以發現的錯誤的意外名稱沖突,明智的做法是使用某種約定來最小化沖突的發生幾率。 可能的約定包括方法名稱使用大寫字母,屬性名稱加上獨特的短字符串前綴(或許只加一個下划線),或者是用動詞來命名方法,而用名詞來命名數據屬性。

數據屬性可以被方法以及一個對象的普通用戶(“客戶端”)所引用。 換句話說,類不能用於實現純抽象數據類型。 實際上,在 Python教程 中沒有任何東西能強制隱藏數據 --- 它是完全基於約定的。 (而在另一方面,用 C 語言編寫的 Python 實現則可以完全隱藏實現細節,並在必要時控制對象的訪問;此特性可以通過用 C 編寫 Python 擴展來使用。)

客戶端應當謹慎地使用數據屬性 --- 客戶端可能通過直接操作數據屬性的方式破壞由方法所維護的固定變量。 請注意客戶端可以向一個實例對象添加他們自己的數據屬性而不會影響方法的可用性,只要保證避免名稱沖突 --- 再次提醒,在此使用命名約定可以省去許多令人頭痛的麻煩。

在方法內部引用數據屬性(或其他方法!)並沒有簡便方式。 我發現這實際上提升了方法的可讀性:當瀏覽一個方法代碼時,不會存在混淆局部變量和實例變量的機會。

方法的第一個參數常常被命名為 self。 這也不過就是一個約定: self 這一名稱在 Python 中絕對沒有特殊含義。 但是要注意,不遵循此約定會使得你的代碼對其他 Python 程序員來說缺乏可讀性,而且也可以想像一個 類瀏覽器 程序的編寫可能會依賴於這樣的約定。

任何一個作為類屬性的函數都為該類的實例定義了一個相應方法。 函數定義的文本並非必須包含於類定義之內:將一個函數對象賦值給一個局部變量也是可以的。 例如:

# 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

 

現在 fg 和 h 都是 C 類的引用函數對象的屬性,因而它們就都是 C 的實例的方法 --- 其中 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)

 

方法可以通過與普通函數相同的方式引用全局名稱。 與方法相關聯的全局作用域就是包含其定義的模塊。 (類永遠不會被作為全局作用域。) 雖然我們很少會有充分的理由在方法中使用全局作用域,但全局作用域存在許多合法的使用場景:舉個例子,導入到全局作用域的函數和模塊可以被方法所使用,在其中定義的函數和類也一樣。 通常,包含該方法的類本身是在全局作用域中定義的,而在下一節中我們將會發現為何方法需要引用其所屬類的很好的理由。

每個值都是一個對象,因此具有  (也稱為 類型),並存儲為 object.__class__ 。


免責聲明!

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



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