文章目錄
- 元類
- 類工廠
- 初始元類
- 元類屬性
- 元類作用
- 面向方面和元類
- 小結
- 新型類
- 新型類VS傳統類
- 靜態方法和類方法
- 特定方法
- 特定屬性
- super()方法
- 小結

元類
既然物件是以類為模板生成的,那么類又是以什么為模板生成的?
事實上絕大部分情況下都都不是必須使用元類才能完成開發,但是元類動態地生成類的能力能更方便地解決下面情景的難題:
- 類在設計時不是所有細節都能確定,有些細節需要程式運行時得到的資訊才能決定,
- 類比實體更重要的情況,如用宣告性語言在類宣告中直接表示了它的程式邏輯,使用元類來影響類的創建程序就相當有用,
類工廠
在Python老版本中,可以使用類工廠函式來創建類,回傳在函式體內動態創建的類,
類工廠的方法是通過一個函式來生產不同的類,類工廠可以是類,就像它們可以是函式一樣容易,
例如:
def class_with_method(func):
class klass: pass
setattr(klass, func.__name__, func)
return klass
def say_tip(self):
print('記得一鍵三連~')
Tip = class_with_method(say_tip)
tip = Tip()
tip.say_tip()
函式class_with_method是一個類工廠函式,通過setattr()方法來設定類的成員函式,并且回傳該類,這個類的成員方法可以通過class_with_method的func引數來指定,

初始元類
在Python2.2之后,type特殊類就是這樣的類工廠,即所謂的元類,元類是類的類,類是元類的實體,物件是類的實體,
元類type使用方法:
def say_tip(self):
print('記得一鍵三連~')
Tip = type('Tip',(),{'say_tip':say_tip})
tip = Tip()
tip.say_tip()

元類type首先是一個類,所以比類工廠的方法梗靈活多變,可以自由的創建子類來繼承擴展元類的能力,例如:
class ChattyTypr(type):
def __new__(cls, name, bases, dct):
print("分配記憶體空間給類",name)
return type.__new__(cls, name, bases, dct)
def __init__(cls, name, bases, dct):
print("初始化類", name)
super(ChattyTypr, cls).__init__(name, bases, dct)
a = ChattyTypr('Test',(),{})

其中,__new__分配創建類和__init__方法配置類是類type內置的基本方法,需要注意的是,第一個蠶食是cls(特指類本身)而非self(類的實體),
元類實體化一個類時,類將會獲得元類所擁有方法,就像類實體化物件時物件獲得類所擁有方法一樣,但是注意多次實體化和多次繼承的區別:

元類屬性
Python中每一個類都是經過元類實體化而來,只不過這個實體化程序在很多情況下都是由Python解釋器自動完成的,那么怎么設定元類的屬性?
每個類都有一個屬性__metaclass__用來說明該類的元類,該屬性一般由解釋器自動設定,不過用戶也可以更改該屬性來更改類的元類,可以在類的內部直接設定__metaclass__屬性,也可以設定全域變數,那么該命名空間下定義所有類的元類都將是全域變數__metaclass__所指定的元類,
class ChattyTypr(type):
def __new__(cls, name, bases, dct):
print("分配記憶體空間給類",name)
return type.__new__(cls, name, bases, dct)
def __init__(cls, name, bases, dct):
print("初始化類", name)
super(ChattyTypr, cls).__init__(name, bases, dct)
class example(metaclass=ChattyTypr):
def __init__(self):
print('初始化')


元類作用
改變全域變數__metaclass就能改變類的元類,而類又是元類的實體化結果,所以元類可以改變類的定義程序,換句話說,只要改變全域變數__metaclass__就能改變類的定義,這就是元類的作用了,
class example:
def __init__(self):
print('類example初始化')
def say_tip(self):
print('記得一鍵三連')
a = example()
a.say_tip()
class change(type):
def __new__(cls, name, bases, dict):
def say_tip(self):
print('記得點贊關注收藏~')
dict['say_tip']=say_tip
return type.__new__(cls, name ,bases, dict)
class example(metaclass=change):
def __init__(self):
print('類example初始化')
def say_tip(self):
print('記得一鍵三連')
a = example()
a.say_tip()

面向方面和元類
元類的作用能帶來什么實用價值嗎?
實際用途確實有的,接近于面向方面編程(Aspect Oriented Programming,AOP)的核心內容,即所謂的“橫切關注點”,
使用面向物件方法構建軟體系統,我們可以利用OO的特性很好地解決縱向問題,因為OO的核心概念(如繼承等)都是縱向結構的,
但是軟體系統中往往很多模塊/類共享某個行為,或者說某個行為存在于軟體的各個部分中,看作是橫向 存在于軟體之中,它所關注的是軟體個部分共有的一些行為,而且很多情況下這種行為不屬于業務邏輯的一部分,
一個軟體系統的業務邏輯很大一部分代碼都是AOP里所說的橫切關注點,例如日志處理、安全檢測、事務處理、權限檢測等占比很大,幾乎每個地方都要呼叫,AOP的思想就是把這些橫切關注點代碼都抽取出來,不再在各個軟體模塊中顯示使用,
以日志處理為例,一般習慣在做一些操作前寫上開始模塊處理的每個步驟都需要由正常日志和例外日志,那么這個軟體光是寫日志的代碼就要成千上萬行了,維護起來相當困難,
如果部分代碼不需要手工寫到各個業務邏輯處理的地方,而是把這部分代碼獨立出來,那么在各個業務邏輯處理的地方,會在運行的時候自動呼叫這些橫切關注點功能,這樣代碼量就少很多,這就是AOP的核心思想,
要實作AOP所說的自動呼叫,有的語言使用AspectJ編譯器,Python則使用元類,
小結
元類具有動態改變類的能力,給編程帶來了更方便的動態性和能力,
實際使用程序中,需要防止過度使用元類來改變類,過于復雜的元類通常會帶來代碼難以和可讀性差的問題,所以一定要在確實需要使用是再使用元類,
新型類
Python在2.2版本后,新引入了兩種不同的類:新型類和傳統類/經典類,Python的物件世界相比也發生了重大變化,
新型類VS傳統類
老版本的Python中不是所有的元素都是物件,內置的數值型別都不能被繼承,而在版本2.2后,任何內建型別也都是繼承自object類的類,凡是繼承自類object或者object子類的類都是新型類,而不是繼承自object或object子類的都成為傳統類,
新的物件模型于傳統模型相比有小但是很重要的優勢,Python版本對傳統類的支持主要是為了兼容性,所以使用類的時候推薦從現在開始直接使用新型類,在Python3版本將放棄兼容性,即Python3.X版本中只存在新型類,
新型類繼承自object或object子類,實際上所有的內建型別都是從object繼承而來,可以用issubclass()函式驗證,當存在子類和父類關系時回傳True,否則回傳False,

(
插播反爬資訊)博主CSDN地址:https://wzlodq.blog.csdn.net/
靜態方法和類方法
新的物件模型提供了兩種類的方法:靜態方法和類方法,
靜態方法可以直接被類或類的實體呼叫,沒有常規方法的那樣限制(系結、非系結、默認第一個引數規則等),即靜態函式的第一個引數不需要指定為self,也不需要只有物件(類的實體)才能呼叫,使用關鍵字@staticmethod定義,
如下定義靜態方法、常規方法(第一個參為self和不帶self兩種)
class Test(object):
@staticmethod
def static_tip(str):
print(str)
def normal_tip(str):
print(str)
def normal_tip2(self,str):
print(str)
- 使用類呼叫
直接使用類呼叫時,不需要傳入self表示具體的類的實體,即報錯只傳了一個引數,

- 使用物件(類的實體)呼叫
使用物件呼叫時,自動將類實體物件作為第一個引數傳給該方法,即報錯給了兩個引數,

類方法不管是使用類來呼叫還是使用物件(類的實體)來呼叫,都是將類作為第一個引數傳入,使用關鍵字@classmethod定義,

特定方法
__new__方法
當一個類C呼叫C(*args,**kwds)創建一個C類實體時,Python內部實際上呼叫的是C.__new__(C,*args,**kwds),new方法的回傳值x就是該類的實體物件,new即用來分配記憶體生成類的實體,
注意第一個引數是cls(即這里寫的類C),用來接受一個類引數,然后才能回傳該類的實體,

使用new方法可以實作一些傳統類無法做到的功能,例如讓類只能實體化一次:

__init__方法
當呼叫new方法分配記憶體創建一個類C物件后,Python判斷該實體是該類的實體,然后會呼叫C.__init__(x,*args,**kwds)來初始化這個實體,x就是new方法的回傳值,init即對類實體物件做初始化操作,
注意第一個引數是self(即這里寫的x)表示接受類的實體物件,

上述實體化物件代碼c = C()就等價于:

__getattribute__方法
__getattribute__負責實作物件屬性參考的全部細節,新型類在呼叫它自身的類或方法是,實際上都是先通過該方法來呼叫,

因為新型類呼叫自身屬性和方法時都會先呼叫__getattribute__方法,所以可以實作一些新功能,如隱藏父類的方法:

特定屬性
內建property類用來系結類實體的方法,并將其回傳值系結為一個類屬性,語法:
attrib = property(fget=None, fset=None, fdel=None, doc=None)
設類C通過property創建了屬性attrib,x是類C的一個實體,
- 當參考
x.attrib時,會呼叫fget()方法取值; - 當為
x.attrib賦值時,會呼叫fset()方法; - 當執行洗掉
del x.attrib時,會呼叫fdel()方法; doc引數為該屬性的檔案字串,
如果不定義fset()和fdel()方法,那么該屬性將是一個只讀屬性,
property可以方便地將一個函式的回傳值轉換為屬性,這下操作就很靈活方便了,
比如定義一個長方形類,如果要將它的面積也作為一個屬性,就可以用property將計算面積的方法系結為一個屬性:
class Rectangle(object):
def __init__(self,width,height):
self.width=width
self.height=height
def getArea(self):
return self.width*self.height
area = property(getArea(),doc='長方形的面積')
上述代碼中,getArea()是計算面積的方法,使用property將該方法的回傳值轉換為屬性area,這樣參考Rectangle的area是,Python會自動使用getArea()計算出面積,同時由于該例中只定義了fget()方法,所以area是一個只讀屬性,
super()方法
新型類提供了一個特殊的方法super(),super(aclass,obj)回傳物件obj是一個特殊的超物件(superobject),當我們呼叫該超物件的一個屬性或方法時,就保證了每個父類的實作均被呼叫且僅僅呼叫了一次,
以下時直接呼叫父類的同名方法,無法避免類A的方法被重復呼叫:
class A(object):
def test(self):
print('A')
class B(A):
def test(self):
print('B')
A.test(self)
class C(A):
def test(self):
print('C')
A.test(self)
class D(B,C):
def test(self):
print('D')
B.test(self)
C.test(self)
d = D()
d.test()

以下時使用super()方法,保證父類方法均呼叫一次:
class A(object):
def test(self):
print('A')
class B(A):
def test(self):
print('B')
super(B, self).test()
class C(A):
def test(self):
print('C')
super(C, self).test()
class D(B,C):
def test(self):
print('D')
super(D, self).test()
d = D()
d.test()

小結
新型類相比于傳統類,支持更多特性和機制,有更多的彈性,例如可以定制實體化的程序,尤其時在多重繼承的情況下能避免傳統類存在的缺陷,而事實上Python3.X版本中已經不存在傳統類了,目前傳統類存在的意義主要是為了保持之前的兼容性,
Python系列博客持續更新中
原創不易,請勿轉載(
本不富裕的訪問量雪上加霜)
博主首頁:https://wzlodq.blog.csdn.net/
微信公眾號:唔仄lo咚鏘
如果文章對你有幫助,記得一鍵三連?
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/261390.html
標籤:python
