python編程基礎——類(高級)

繼承

派生類定義的語法如下:

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

其中BaseClassName必須定義在包含派生類定義的作用域中。什麼意思呢?就是說派生類必須能夠在當前的作用域中找到其要繼承的基類。如果基類不在當前作用域中,而是在另一個模塊中,這時候就需要這樣定義了。

class DerivedClassName(modname.BaseClassName):

派生類在定義過程中和基類是一樣的。在構造類對象時,基類會被記住。此信息將被用來解析屬性引用:如果請求的屬性在類中沒有找到,那麼就去基類中找,如果基類也是派生其他類,則轉入基類的基類中尋找,以此類推。
在派生類中可能會重載其基類的方法。因爲方法在調用同一對象的其他方法時沒有特殊權限,調用同一基類中定義的另一方法的基類方法最終可能會調用覆蓋它的派生類的方法。這種覆蓋只會看名稱相同與否,相同則覆蓋,與參數個數無關。

class A:
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def add(self):
        print("A類中的方法")

class B(A):
    def add(self):
        print("B類中的方法")
b=B(1,2) #因爲派生類中沒有初始化函數,轉到基類找到__init__()
b.add() #B中的add()覆蓋了A中的add
# 輸出結果
B類中的方法

如果我我們想在派生類中對基類的函數進行擴充,即只需要使用BaseClassName.methodname(self, arguments)即可。(這種方式與是不是派生沒有關係)。使用super().methodname(argumenrs)也是可以的(這種方式僅限於調用父類的方法)。

class A:
    def add(self):
        print("A類中的方法")

class B(A):
    def add(self):
        A.add(self) # 或者super().add()
        print("B類中的方法")
a=B()
a.add()    
# 輸出結果
A類中的方法
B類中的方法

實例與繼承的判斷

我們可以通過isinstance(a,A)來檢查一a實例是不是A類型。

a=5
b=3.14
print(isinstance(a,int),isinstance(b,int))
#輸出結果
True False

我們可以通過issubclass(B,A)來檢測B是不是派生於A。

class A:
    pass
class B(A):
    pass
print(issubclass(B,A),issubclass(A,B))
#輸出結果
True False


多重繼承

Python也支持多種繼承形式。具有多個基類的類定義如下所示:

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

對於多數情況來說,搜索操作是深度優先的,從左至右進行,當遇到相同的名稱時,只會保留第一次遇到的。

class A:
    def app(self):
        print("a_app")
class B:
    add='B'
    def add(self):
        print("b_add")
    def app(self):
        print("b_app")
class Ain(A):
    def add(self):
        print("ain_add")
class Bin(B):
    def app(self):
        print("bin_app")
class C(Ain,Bin):
    pass
class D(Bin,Ain):
    pass
c=C()
d=D()
c.add(),c.app()
d.add(),d.app()

#輸出結果
ain_add
a_app
b_add
bin_app

嗯哼,我想上面的代碼看起來肯定有點迷,於是畫了一張圖,從下面的圖中使用深度優先,我相信你可以很清楚的理解它。
在這裏插入圖片描述



私有化

在Python中私有化名稱是比較簡單的。就是在要私有化的名稱前面加上“__”便可以了。(值得注意的是,如果名稱前後都加“__”則不會私有化)

class A:
    def __add(self):
        print("A類中的方法")
a=A()
a.__add()   
#輸出結果
AttributeError: 'A' object has no attribute '__add'

雖然在類外不能夠再訪問函數了,但在類內還是可以訪問的。

class A:
    def __add(self):
        print("A類中的私有方法")
    def app(self):
        self.__add()
        print("常規方法")
a=A()
a.app()  
#輸出結果
A類中的私有方法
常規方法


迭代器

對於for循環的容器對象來說,容器對象是可迭代的。事實上,for語句會調用容器對象中的iter()。該函數返回一個定義了__next__()方法的迭代器對象,該方法將逐一訪問容器中的元素。當元素耗盡時,__next()__將會引發StopIteration異常來終止for循環。
瞭解迭代器機制後,給自定義類添加迭代器就比較容易了。定義一個__iter()__()方法來返回一個帶有__next__()方法的對象。如果類已經定義了__next()__,則__iter__()可以簡單地返回self。

class A:
    def __init__(self,name):
        self.name=name
        self.index=len(name)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index==0:
            raise StopIteration
        self.index = self.index - 1
        return self.name[self.index]
a=A(['asd','q',1,2])
print(iter(a))
for i in a:
    print(i)
# 輸出結果
2
1
q
asd


生成器

生成器是一個用於創建迭代器的簡單而強大的工具,它的寫法類似函數,但返回數據時使用的不是return而是yield語句。每次生成器調用next()時,他會從上次離開位置執行(它會記錄上次執行語句時的所有值)

我們可以看到return返回的僅僅是一個值,而yield返回的是一個可生成器對象,是所有的執行結果,對於這一點我們可以通過list來查看。

def ret():
    for i in range(5,1,-1):
        return i
def yie():
    for i in range(5,1,-1):
        yield i
print(ret())
print(list(yie()),yie())
#輸出結果
5
[5, 4, 3, 2] <generator object yie at 0x0000027FF69230C0>

生成器的操作是可以用迭代器來完成的,不同的是生成器在寫法上要比迭代器更加簡單清晰,因爲它會自動創建__iter__()__next()__方法。


生成器表達式

某些簡單的生成器可以寫成簡單的表達式代碼;

>>> a=[i*i for i in range(10)]
>>> a
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> a=[1,2,4]
>>> b=[2,2,5]
>>> c=[x*y for x,y in zip(a,b)]
>>> c
[2, 4, 20]

>>> c=[x*y for x in a for y in b]
>>> c
[2, 2, 5, 4, 4, 10, 8, 8, 20]

>>> a = 'golf'
>>> list(a[i] for i in range(len(a)-1, -1, -1))
['f', 'l', 'o', 'g']

注意:迭代器和生成器都是執行的結果,不像列表一次性直接加載出來



雜項

dict

一個字典或其他類型的映射對象,用於存儲對象的(可寫)屬性。

class A:
    "自定義類A"
    st='hello'
    def add(self,name):
        self.name=name
a=A()
print(A.__dict__)  
print(a.__dict__) 
# 輸出結果
{'__module__': '__main__', 
 '__doc__': '自定義類A',
 'st': 'hello',
 'add': <function A.add at 0x007C37C8>, 
 '__dict__': <attribute '__dict__' of 'A' objects>, 
 '__weakref__': <attribute '__weakref__' of 'A' objects>}
{}

不難看出,__dict__以字典的形式返回當前類中的存在的熟悉名稱(key)以及對應的值(value)。你可能會奇怪爲什麼實例化變量a的結果是空的。這是因爲實例化的過程僅返回該類的一個新實例,所以實例化的屬性在沒有調用前是不會生產的(詳細參見類(基礎)的實例化),此時僅__init__函數會在實例化過程中被調用。因此有屬性值age,但類A則不會有該屬性,這是因爲age是在實例化中產生的,是實例化變量的熟悉,與類A無關。

	...
	def __init__(self):
		self.age=5
	...
{'__module__': '__main__', '__doc__': '自定義類A', 
 'st': 'hello', '__init__': <function A.__init__ at 0x007C37C8>, 
 'add': <function A.add at 0x007C3810>, 
 '__dict__': <attribute '__dict__' of 'A' objects>, 
 '__weakref__': <attribute '__weakref__' of
'A' objects>}
{'age': 5}

當我們在實例化調用st時,因爲在實例化中沒有找到,因此轉向類中尋找。因此a.st指向了類屬性st,因此我們通過賦值來建立實例屬性。只有通過賦值才能在實例中新建一個同名屬性。

...
print(a.st)
print(a.__dict__)
a.st='hello world'
print(a.st)
print(a.__dict__) 
# 輸出結果
hello
{'age': 5}
hello world
{'age': 5, 'st': 'hello world'}

因此,對於類和實例來說,實例化優先訪問自己的屬性,後訪問類屬性,在類中定義的屬性可以被實例化調用,反之則不行。


solts

slots 允許我們顯式地聲明數據成員(例如特徵屬性)並禁止創建 dictweakref (除非是在 slots 中顯式地聲明或是在父類中可用。)

相比使用 dict 此方式可以顯著地節省空間。 屬性查找速度也可得到顯著的提升。

我們通過 slots 爲類P顯示定義了兩個屬性ench。與 dict 不同的是,即便實例化後,也會返回一個和類一樣的元組對象。

#顯示定義了兩個數據屬性
class P:
    __slots__=("en","ch")
print(P.__slots__)
p=P()
print(p.__slots__)
#運行結果
('en', 'ch')
('en', 'ch')

那麼 __sorts__ 有什麼用呢?
  • 限制實例創建與類同名的屬性
class P1:
    __slots__=("en","ch")
    en='asd'
    def __init__(self):
        self.en='asd_init'
p=P1()
#運行結果
ValueError: 'en' in __slots__ conflicts with class variable
  • 接管實例屬性
class P1:
    __slots__=("en","ch")
    def __init__(self):
        self.en='asd_init'
p=P1()
print(p.en)
P1.en='asd'
print(p.en)
del P1.en
print(p.en)
#運行結果
asd_init
asd
AttributeError: 'P1' object has no attribute 'en'
  • 限制實例修改屬性
class P1:
    __slots__=("en","ch")
p=P1()
P1.en="asd"
print(p.en)
P1.en="qwe"
print(p.en)
p.en="asd"
print(p.en)
#運行結果
asd
qwe
AttributeError: 'P1' object attribute 'en' is read-only

通過 __slots__ 可以讓類管控實例,本質上是起到了優化內存的效果。(因爲類和實例公用一個存儲空間,且該存儲空間由類管理)

setattr

object.__setattr__(self, name, value)
此方法在一個屬性被嘗試賦值時被調用。這個調用會取代正常機制(即將值保存到實例字典)。 name 爲屬性名稱, value 爲要賦給屬性的值。

class P:
    def __setattr__(self,name,value):
        value+='__調用setattr函數寫入dict'
        self.__dict__[name]=value
p=P()
p.n="asd"
print(p.n)
#輸出結果
asd__調用setattr函數寫入dict


getattr

object.__getattr__(self, name)
當訪問屬性失敗時被調用,返回屬性值或時引發一個AttributeError異常。注意如果屬性是通過正常機制找到的,getattr()就不會被調用

class P:
    def __getattr__(self,name):
        return "調用的屬性不存在"
p=P()
print(p.n)
#運行結果
調用的屬性不存在


getattribute

object.__getattribute__(self, name)
此方法會無條件地被調用以實現對類實例屬性的訪問。如果類還定義了 getattr(),則後者不會被調用,除非 getattribute() 顯式地調用它或是引發了 AttributeError。

class P:
    def __getattr__(self,name):
        return "調用的屬性不存在"
    def __getattribute__(self,name):
        return object.__getattribute__(self,name)
p=P()

#調用未創建的屬性
print(p.n)
#運行結果
函數getattribute
調用的屬性不存在

#先創建屬性再嘗試調用它
p.n='asd'
print(p.n)
#運行結果
函數getattribute
asd

Q: 可以使用return self.__dict__[name]代替return object.__getattribute__(self,name)嗎?

A: 不可以,因爲如果用這樣的方式就是訪問 self.__dict__,只要是訪問類的某個屬性,就要調用__getattribute__,會導致循環下去。

class P:
    def __getattribute__(self,name):
        return self.__dict__[name]
p=P()
p.n='asd'
print(p.n)
#輸出結果
···
  return self.__dict__[name]
 [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Q: 爲什麼要用 object.__getattribute__(self,name)呢?
A: object類有__getattribute__屬性,因此所有的類默認就有__getattribute__屬性(所有類都繼承自object),object的__getattribute__起什麼用呢?它做的就是查找自定義類的屬性,如果屬性存在則返回屬性的值,如果不存在則拋出AttributeError。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章