您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何進行python中類的全面分析,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
面向對象重要的概念就是類(Class)和實例(Instance),類是抽象的模板,而實例是根據類創建出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的數據可能不同。
先回顧下 OOP 的常用術語:
類:對具有相同數據和方法的一組對象的描述或定義。
對象:對象是一個類的實例。
實例(instance):一個對象的實例化實現。
實例屬性(instance attribute):一個對象就是一組屬性的集合。
實例方法(instance method):所有存取或者更新對象某個實例一條或者多條屬性的函數的集合。
類屬性(classattribute):屬于一個類中所有對象的屬性,不會只在某個實例上發生變化
類方法(classmethod):那些無須特定的對象實例就能夠工作的從屬于類的函數。
類概述
在Python中,定義類是通過class關鍵字:
class Student(object): pass
class后面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的。通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
定義好了Student類,就可以根據Student類創建出Student的實例,創建實例是通過類名+()實現的:
>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>
可以看到,變量bart指向的就是一個Student的object,后面的0x10a67a590是內存地址,每個object的地址都不一樣,而Student本身則是一個類。
可以自由地給一個實例變量綁定屬性,比如,給實例bart綁定一個name屬性:
>>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson'
由于類可以起到模板的作用,因此,可以在創建實例的時候,把一些我們認為必須綁定的屬性強制填寫進去。通過定義一個特殊的init方法,在創建實例的時候,就把name,score等屬性綁上去。
class Student(object): def __init__(self, name, score): self.name = name self.score = score
注意到init方法的***個參數永遠是self,表示創建的實例本身,因此,在init方法內部,就可以把各種屬性綁定到self,因為self就指向創建的實例本身。
有了init方法,在創建實例的時候,就不能傳入空的參數了,必須傳入與init方法匹配的參數,但self不需要傳,Python解釋器自己會把實例變量傳進去:
>>> bart = Student('Bart Simpson', 59) >>> bart.name 'Bart Simpson' >>> bart.score 59
和普通的函數相比,在類中定義的對象函數(還有靜態方法,類方法)只有一點不同,就是***個參數永遠是實例變量self,并且,調用時不用傳遞該參數。
新式類、舊式類
python的新式類是2.2版本引進來的,之前的類叫做經典類或者舊類。Python 2.x 中如果一個類繼承于一個基類(可以是自定義類或者其它類)或者繼承自 object,則該類為新式類;沒有繼承的類為經典類。Python 3.x 則全部為新式類。
新式類被賦予了很多新的特性(如:統一了types和classes),并改變了以往經典類的一些內容(如:改變了多繼承下方法的執行順序)。
關于統一類(class)和類型(type),具體看下面的例子
class OldClass(): pass o = OldClass() print o.__class__ # __main__.OldClass print type(o) # <type 'instance'> class newClass(object): pass n = newClass() print n.__class__ # <class '__main__.newClass'> print type(n) # <class '__main__.newClass'>
對象屬性
Python 中對象的屬性包含對象的所有內容:方法和數據,注意方法也是對象的屬性。查找對象的屬性時,首先在對象的__dict__ 里面查找,然后是對象所屬類的dict,再往后是繼承體系中父類(MRO解析)的dict,任意一個地方查找到就終止查找,并且調用 __getattribute__(也有可能是__getattr__) 方法獲得屬性值。
方法
在 Python 類中有3種方法,即靜態方法(staticmethod),類方法(classmethod)和實例方法:
對于實例方法,在類里每次定義實例方法的時候都需要指定實例(該方法的***個參數,名字約定成俗為self)。這是因為實例方法的調用離不開實例,我們必須給函數傳遞一個實例。假設對象a具有實例方法 foo(self, *args, **kwargs),那么調用的時候可以用 a.foo(*args, **kwargs),或者 A.foo(a, *args, **kwargs),在解釋器看來它們是完全一樣的。
類方法每次定義的時候需要指定類(該方法的***個參數,名字約定成俗為cls),調用時和實例方法類似需要指定一個類。
靜態方法其實和普通的方法一樣,只不過在調用的時候需要使用類或者實例。之所以需要靜態方法,是因為有時候需要將一組邏輯上相關的函數放在一個類里面,便于組織代碼結構。一般如果一個方法不需要用到self,那么它就適合用作靜態方法。
具體的例子如下:
def foo(x): print "executing foo(%s)"%(x) class A(object): def foo(self): print "executing foo(%s)" % self @classmethod def class_foo(cls): print "executing class_foo(%s)" % cls @staticmethod def static_foo(): print "executing static_foo()" a = A() print a.foo print A.foo print a.class_foo print A.class_foo print A.static_foo print a.static_foo print foo # <bound method A.foo of <__main__.A object at 0x10d5f90d0>> # <unbound method A.foo> # <bound method type.class_foo of <class '__main__.A'>> # <bound method type.class_foo of <class '__main__.A'>> # <function static_foo at 0x10d5f32a8> # <function static_foo at 0x10d5f32a8> # <function foo at 0x10d5f1ed8>
在訪問類方法的時候有兩種方法,分別叫做 未綁定的方法(unbound method) 和 綁定的方法(bound method):
未綁定的方法:通過類來引用實例方法返回一個未綁定方法對象。要調用它,你必須顯示地提供一個實例作為***個參數,比如 A.foo。
綁定的方法:通過實例訪問方法返回一個綁定的方法對象。Python自動地給方法綁定一個實例,所以調用它時不用再傳一個實例參數,比如 a.foo。
數據屬性
下面創建了一個Student的類,并且實現了這個類的初始化函數"__init__":
class Student(object): count = 0 books = [] def __init__(self, name, age): self.name = name self.age = age
在上面的Student類中,count, books, name 和 age 都被稱為類的數據屬性,但是它們又分為類數據屬性和實例數據屬性。直接定義在類體中的屬性叫類屬性,而在類的方法中定義的屬性叫實例屬性。
首先看下面代碼,展示了對類數據屬性和實例數據屬性的訪問:
Student.books.extend(["python", "javascript"]) print "Student book list: %s" %Student.books # class can add class attribute after class definition Student.hobbies = ["reading", "jogging", "swimming"] print "Student hobby list: %s" %Student.hobbies print dir(Student) # class instance attribute wilber = Student("Wilber", 28) print "%s is %d years old" %(wilber.name, wilber.age) # class instance can add new attribute # "gender" is the instance attribute only belongs to wilber wilber.gender = "male" print "%s is %s" %(wilber.name, wilber.gender) # class instance can access class attribute wilber.books.append("C#") print wilber.books
通過內建函數dir(),或者訪問類的字典屬性__dict__,這兩種方式都可以查看類或者實例有哪些屬性。對于類數據屬性和實例數據屬性,可以總結為:
類數據屬性屬于類本身,可以通過類名進行訪問/修改;
類數據屬性也可以被類的所有實例訪問/修改;
在類定義之后,可以通過類名動態添加類數據屬性,新增的類屬性也被類和所有實例共有;
實例數據屬性只能通過實例訪問;
在實例生成后,還可以動態添加實例數據屬性,但是這些實例數據屬性只屬于該實例;
再看下面的程序
class Person: name="aaa" p1=Person() p2=Person() p1.name="bbb" print p1.name # bbb print p2.name # aaa print Person.name # aaa
上面程序中,p1.name="bbb"是實例調用了類變量,p1.name一開始是指向的類變量name="aaa",但是在實例的作用域里把類變量的引用改變了,就變成了一個實例變量。self.name不再引用Person的類變量name了。
class Person: name=[] p1=Person() p2=Person() p1.name.append(1) print p1.name # [1] print p2.name # [1] print Person.name # [1]
特殊的類屬性
對于所有的類,都有一組特殊的屬性:
通過這些屬性,可以得到 Student類的一些信息,如下:
類的繼承
Python 是面向對象語言,支持類的繼承(包括單重和多重繼承),繼承的語法如下:
class DerivedClass(BaseClass1, [BaseClass2...]): <statement-1> . <statement-N>
子類可以覆蓋父類的方法,此時有兩種方法來調用父類中的函數:
調用父類的未綁定的構造方法。在調用一個實例的方法時,該方法的self參數會被自動綁定到實例上(稱為綁定方法)。但如果直接調用類的方法(比如A.init),那么就沒有實例會被綁定。這樣就可以自由的提供需要的self參數,這種方法稱為未綁定(unbound)方法。大多數情況下是可以正常工作的,但是多重繼承的時候可能會重復調用父類。
通過 super(cls, inst).method() 調用 MRO中下一個類的函數,這里有一個非常不錯的解釋,看完后對 super 應該就熟悉了。
未綁定(unbound)方法調用如下:
class Base(object): def __init__(self): print("Base.__init__") class Derived(Base): def __init__(self): Base.__init__(self) print("Derived.__init__")
supper 調用如下:
class Base(object): def __init__(self): print "Base.__init__" class Derived(Base): def __init__(self): super(Derived, self).__init__() print "Derived.__init__" class Derived_2(Derived): def __init__(self): super(Derived_2, self).__init__() print "Derived_2.__init__"
繼承機制 MRO
MRO 主要用于在多繼承時判斷調用的屬性來自于哪個類。Python2.2以前的類為經典類,它是一種沒有繼承的類,實例類型都是type類型,如果經典類被作為父類,子類調用父類的構造函數時會出錯。這時MRO的方法為DFS(深度優先搜索),子節點順序:從左到右。inspect.getmro(A)可以查看經典類的MRO順序。
兩種繼承模式在DFS下的優缺點:
***種,兩個互不相關的類的多繼承,這種情況DFS順序正常,不會引起任何問題;
第二種,棱形繼承模式,存在公共父類(D)的多繼承,這種情況下DFS必定經過公共父類(D)。如果這個公共父類(D)有一些初始化屬性或者方法,但是子類(C)又重寫了這些屬性或者方法,那么按照DFS順序必定是會先找到D的屬性或方法,那么C的屬性或者方法將永遠訪問不到,導致C只能繼承無法重寫(override)。這也就是新式類不使用DFS的原因,因為他們都有一個公共的祖先object。
為了使類和內置類型更加統一,Python2.2版本引入了新式類。新式類的每個類都繼承于一個基類,可以是自定義類或者其它類,默認承于object,子類可以調用父類的構造函數。可以用 A.__mro__ 可以查看新式類的順序。
在 2.2 中,有兩種MRO的方法:
如果是經典類MRO為DFS;
如果是新式類MRO為BFS(廣度優先搜索),子節點順序:從左到右。
新式類兩種繼承模式在BFS下的優缺點:
***種,正常繼承模式。比如B明明繼承了D的某個屬性(假設為foo),C中也實現了這個屬性foo,那么BFS明明先訪問B然后再去訪問C,但是A的foo屬性是c,這個問題稱為單調性問題。
第二種,棱形繼承模式,BFS的查找順序解決了DFS順序中的只能繼承無法重寫的問題。
因為DFS 和 BFS 都存在較大的問題,所以從Python2.3開始新式類的 MRO采用了C3算法,解決了單調性問題,和只能繼承無法重寫的問題。MRO的C3算法順序如下圖:
C3 采用圖的拓撲排序算法,具體實現可以參考官網文檔。
多態
多態即多種形態,在運行時確定其狀態,在編譯階段無法確定其類型,這就是多態。Python中的多態和Java以及C++中的多態有點不同,Python中的變量是動態類型的,在定義時不用指明其類型,它會根據需要在運行時確定變量的類型。
Python本身是一種解釋性語言,不進行預編譯,因此它就只在運行時確定其狀態,故也有人說Python是一種多態語言。在Python中很多地方都可以體現多態的特性,比如內置函數len(object),len函數不僅可以計算字符串的長度,還可以計算列表、元組等對象中的數據個數,這里在運行時通過參數類型確定其具體的計算過程,正是多態的一種體現。
特殊的類方法
類中經常有一些方法用兩個下劃線包圍來命名,下圖給出一些例子。合理地使用它們可以對類添加一些“魔法”的行為。
構造與析構
當我們調用 x = SomeClass() 的時候,***個被調用的函數是 __new__ ,這個方法創建實例。接下來可以用 __init__ 來指明一個對象的初始化行為。當這個對象的生命周期結束的時候, __del__ 會被調用。
__new__(cls,[...]) 是對象實例化時***個調用的方法,它只取下 cls 參數,并把其他參數傳給init。
__init__(self,[...]) 為類的初始化方法。它獲取任何傳給構造器的參數(比如我們調用 x = SomeClass(10, ‘foo’) ,init 函數就會接到參數 10 和 ‘foo’) 。
__del__(self):new和init是對象的構造器, del則是對象的銷毀器。它并非實現了語句 del x (因此該語句不等同于x.__del__()),而是定義當對象被回收時的行為。
當我們創建一個類的實例時,首先會調用new創建實例,接著才會調用init來進行初始化。不過注意在舊式類中,實例的創建并沒有調用new方法,如下例子:
class A: def __new__(cls): print "A.__new__ is called" # -> this is never called A()
對于新式類來說,我們可以覆蓋new方法,注意該方法的***個參數cls(其實就是當前類類型)用來指明要創建的類型,后續參數用來傳遞給init進行初始化。如果new返回了cls類型的對象,那么接下來調用init,否則的話不會調用init(調用該方法必須傳遞一個實例對象)。
class A(object): # -> don't forget the object specified as base def __new__(cls): print "A.__new__ called" return super(A, cls).__new__(cls) def __init__(self): print "A.__init__ called" A() # A.__new__ called # A.__init__ called
這里我們調用super()來獲取 MRO 中A的下一個類(在這里其實就是基類 object)的new方法來創建一個cls的實例對象,接著用這個對象來調用了init。下面的例子中,并沒有返回一個合適的對象,所以并沒有調用init:
class Sample(object): def __str__(self): return "SAMPLE" class A(object): def __new__(cls): print "A.__new__ called" return super(A, cls).__new__(Sample) # return Sample() def __init__(self): print "A.__init__ called" # -> is actually never called a = A() # A.__new__ called
關于 super,這里是一個非常不錯的解釋,簡單來說super做了下面的事情:
def super(cls, inst): mro = inst.__class__.mro() return mro[mro.index(cls) + 1]
關于 MRO,這篇文章非常棒:你真的理解Python中MRO算法嗎?,簡單來說,在新式類MRO的 C3 算法中,保證:基類永遠出現在派生類后面,如果有多個基類,基類的相對順序保持不變。
操作符
利用特殊方法可以構建一個擁有Python內置類型行為的對象,這意味著可以避免使用非標準的、丑陋的方式來表達簡單的操作。在一些語言中,這樣做很常見:
if instance.equals(other_instance): # do something
Python中當然也可以這么做,但是這樣做讓代碼變得冗長而混亂。不同的類庫可能對同一種比較操作采用不同的方法名稱,這讓使用者需要做很多沒有必要的工作。因此我們可以定義方法__eq__,然后就可以像下面這樣使用:
if instance == other_instance: # do something
Python 有許多特殊的函數對應到常用的操作符上,比如:
__cmp__(self, other):定義了所有比較操作符的行為。應該在 self < other 時返回一個負整數,在 self == other 時返回0,在 self > other 時返回正整數。
__eq__(self, other):定義等于操作符(==)的行為。
__ne__(self, other):定義不等于操作符(!=)的行為(定義了 eq 的情況下也必須再定義 ne!)
__le__(self, other):定義小于等于操作符(<)的行為。
__ge__(self, other):定義大于等于操作符(>)的行為。
數值操作符
就像可以使用比較操作符來比較類的實例,也可以定義數值操作符的行為。可以分成五類:一元操作符,常見算數操作符,反射算數操作符,增強賦值操作符,和類型轉換操作符,下面為一些例子:
__pos__(self) 實現取正操作,例如 +some_object
__invert__(self) 實現取反操作符 ~
__add__(self, other) 實現加法操作
__sub__(self, other) 實現減法操作
__radd__(self, other) 實現反射加法操作
__rsub__(self, other) 實現反射減法操作
__floordiv__(self, other) 實現使用 // 操作符的整數除法
__iadd__(self, other) 實現加法賦值操作。
__isub__(self, other) 實現減法賦值操作。
__int__(self) 實現到int的類型轉換。
__long__(self) 實現到long的類型轉換。
反射運算符方法和它們的常見版本做的工作相同,只不過是處理交換兩個操作數之后的情況。類型轉換操作符,主要用于實現類似 float() 這樣的內建類型轉換函數的操作。
類的表示
使用字符串來表示類是一個相當有用的特性。在Python中有一些內建方法可以返回類的表示,相對應的,也有一系列特殊方法可以用來自定義在使用這些內建函數時類的行為。
__str__(self) 定義對類的實例調用 str() 時的行為。
__repr__(self) 定義對類的實例調用 repr() 時的行為。 str() 和 repr() 最主要的差別在于“目標用戶”,repr() 的作用是產生機器可讀的輸出(大部分情況下,其輸出可以作為有效的Python代碼),而 str() 則產生人類可讀的輸出。
__dir__(self) 定義對類的實例調用 dir() 時的行為,這個方法應該向調用者返回一個屬性列表。如果重定義了__getattr__ 或者使用動態生成的屬性,以實現類的交互式使用,那么這個方法是必不可少的。
屬性控制
在Python中,重載__getattr__、__setattr__、__delattr__和__getattribute__方法可以用來管理一個自定義類中的屬性訪問。其中:
getattr方法將攔截所有未定義的屬性獲取(當要訪問的屬性已經定義時,該方法不會被調用,至于定義不定義,是由Python能否查找到該屬性來決定的);
getattribute方法將攔截所有屬性的獲取(不管該屬性是否已經定義,只要獲取它的值,該方法都會調用),由于此情況,所以,當一個類中同時重載了getattr和getattribute方法,那么getattr永遠不會被調用,另外getattribute方法僅僅存在于Python2.6的新式類和Python3的所有類中;
setattr方法將攔截所有的屬性賦值;
delattr方法將攔截所有的屬性刪除。
在Python中,一個類或類實例中的屬性是動態的(因為Python是動態的),也就是說,可以往一個類或類實例中添加或刪除一個屬性。
由于getattribute、setattr、delattr方法對所有的屬性進行攔截,所以,在重載它們時,不能再像往常的編碼,要注意避免遞歸調用(如果出現遞歸,則會引起死循環);然而對getattr方法,則沒有這么多的限制。
在重載setattr方法時,不能使用“self.name = value”格式,否則,它將會導致遞歸調用而陷入死循環。正確的應該是:
def __setattr__(self, name, value): # do-something object.__setattr__(self, name, value) # do-something
其中的object.__setattr__(self, name, value)一句可以換成self.__dict__[name] = value;但前提是,必須保證getattribute方法重載正確(如果重載了getattribute方法的話),否則,將在賦值時導致錯誤,因為self.dict將要觸發對self所有屬性中的dict屬性的獲取,這樣從而就會引發getattribute方法的調用,如果getattribute方法重載錯誤,setattr方法自然而然也就會失敗。
自定義序列
有許多辦法可以讓 Python 類表現得像是內建序列類型(字典,元組,列表,字符串等)。
在Python中實現自定義容器類型需要用到一些協議。首先,不可變容器類型有如下協議:想實現一個不可變容器,你需要定義__len__ 和 __getitem__。
可變容器的協議除了上面提到的兩個方法之外,還需要定義 __setitem__ 和 __delitem__ 。如果你想讓你的對象可以迭代,你需要定義 __iter__ ,這個方法返回一個迭代器。迭代器必須遵守迭代器協議,需要定義 __iter__ (返回它自己)和 next 方法。
上下文管理
上下文管理協議(Context Management Protocol)包含方法 __enter__() 和 __exit__(),支持 該協議的對象要實現這兩個方法。
enter: 進入上下文管理器的運行時上下文。如果指定了 as 子句的話,返回值賦值給 as 子句中的 target。
exit: 退出與上下文管理器相關的運行時上下文。返回一個布爾值表示是否對發生的異常進行處理。
在執行with語句包裹起來的代碼塊之前會調用上下文管理器的 enter 方法,執行完語句體之后會執行 exit 方法。
with 語句的語法格式如下:
with context_expression [as target(s)]: with-body
Python 對一些內建對象進行改進,加入了對上下文管理器的支持,可以用于 with 語句中,比如可以自動關閉文件、線程鎖的自動獲取和釋放等。如下面例子:
>>> with open("etc/CS.json") as d: ...: print d <open file 'etc/CS.json', mode 'r' at 0x109344540> >>> print d <closed file 'etc/CS.json', mode 'r' at 0x109344540> >>> print dir(d) ['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', ...]
通過使用 with 語句,不管在處理文件過程中是否發生異常,都能保證 with 語句執行完畢后已經關閉了打開的文件句柄。
上述內容就是如何進行python中類的全面分析,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。