面向物件的雙下方法,元類
阿新 • • 發佈:2022-04-12
今日作業
編寫元類規定物件的所有資料值轉大寫 eg: obj.name = 'jason' print(obj.name) # JASON class MyTypeClass(type): def __call__(self, *args, **kwargs): args1 = [] kwargs1 = {} if args: for i in range(len(args)): args1.append(args[i].upper()) print(args1) if kwargs: for i in kwargs: kwargs1[i] = kwargs[i].upper() print(kwargs1) return super().__call__(*args1, **kwargs1) class MyClass(metaclass=MyTypeClass): def __init__(self, name): self.name = name my = MyClass('aaa') print(my.name)
面向物件的雙下方法
1.__ str__, __ repr__
__str__(***) 裡邊必須有return "字串"型別 (優先順序高於__repr__) 1.列印物件的時候,會觸發__str__方法 2.直接str轉化也可以觸發 class A: def __init__(self,name,age): self.name = name self.age =age def __str__(self): print(666) return f'姓名: {self.name} 年齡: {self.age}' a = A('二狗',35) b = A('大狗',56) c = A('老狗',18) 列印物件觸發__str__方法 print(f'{a.name} {a.age}') # 二狗 35 print(f'{b.name} {b.age}') print(f'{c.name} {c.age}') print(a) # 666 姓名: 二狗 年齡: 35 print(b) print(c) 直接str轉化也可以觸發. print(str(a)) # 666 姓名: 二狗 年齡: 35 __repr__ 裡邊必須有return "字串"型別 列印物件的時候,會觸發__repr__方法 class A: def __init__(self,name,age): self.name = name self.age =age def __repr__(self): print(666) return f'姓名: {self.name} 年齡: {self.age}' a = A('二狗',35) b = A('大狗',56) c = A('老狗',18) print(a) print(repr(a)) __str__和__repr__ 一起使用時,只執行__str__的內容 class A: def __init__(self,name,age): self.name = name self.age =age def __str__(self): return '777' def __repr__(self): return '666' a = A('二狗',35) b = A('大狗',56) c = A('老狗',18) # print(a) print(a) # 777 print(b) # 777 ''' str()方法定義當被print()方法或者str()方法呼叫時的行為,repr()方法定義當被print()方法或者 repr()方法呼叫時的行為,一般來說,它們的功能都是實現類到字串的轉化,實現方式上也沒有特別的差 異。 但實際上,str()方法的返回結果可讀性應該要更強一些,repr()方法的返回結果要更加準確,也就是 說,str()方法的意義是得到便於人們閱讀的資訊,而__repr__()方法的目的在於開發者便於除錯。 注意:當進行print()的時候,首先被執行的是__str__()方法,如果沒定義__str__()方法,repr() 方法才會被執行。 '''
2.__call__
__call__(***) 物件()觸發物件從屬類(父類)的__call__方法
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo()
obj()
當呼叫obj()時,call()方法會被呼叫,call()方法也可以接受引數
'''
call()方法可以讓類的例項像函式一樣被呼叫
'''
3. __del__
__del__當一個物件的生命週期結束被徹底銷燬的時候被調,__del__方法會被呼叫,不常用,一般不需要 進行重寫。 class A: def __del__(self): print(666) obj = A() del obj
4.__getattr__,__getattribute__,__setattr__
__getattr__表示的是當一個物件訪問一個屬性時,沒有從它定義的屬性當中查詢到就會呼叫這個方法。
class A(object):
def __init__(self, value):
self.value = value
def __getattr__(self, item):
print("into __getattr__")
return "can not find"
a = A(10)
print(a.value)
# 10
print(a.name)
# into __getattr__
# can not find
__getattribute__只要物件查詢名字無論名字是否存在都會執行該方法,如果類中有__getattribute__方
法 那麼就不會去執行__getattr__方法
class A(object):
def __init__(self, value):
self.value = value
def __getattribute__(self, item):
print("into __getattribute__")
return "__getattribute__"
a = A(10)
print(a.value)
into __getattribute__
__getattribute__
print(a.name)
into __getattribute__
__getattribute__
class A(object):
def __init__(self, value):
self.value = value
def __getattribute__(self, item):
print("into __getattribute__")
return "__getattribute__"
def __getattr__(self, item):
print('into __getattr__')
return '__getattr__'
a = A(10)
print(a.value)
# into __getattribute__
# __getattribute__
print(a.name)
# into __getattribute__
# __getattribute__
'''
使用歸納:
1.__getattribute__方法優先順序比__getattr__高
2.當同時定義__getattribute__和__getattr__時,__getattr__方法不會再被呼叫,除非顯示呼叫
__getattr__方法或引發AttributeError異常。
3.如果是對不存在的屬性做處理,儘量把邏輯寫在__getattr__方法中
4.如果非得重寫__getattribute__方法,需要注意兩點:第一是避免.操作帶來的死迴圈;第二是不要遺
忘父類的__getattribute__方法在子類中起的作用
'''
__setattr__物件在執行新增屬性操作的時候自動觸發 >>> obj.變數名=變數值
class A(object):
def __init__(self, value):
print("into __init__")
self.value = value
def __setattr__(self, name, value):
print("into __setattr__")
if value == 10:
print("from __init__")
object.__setattr__(self, name, value)
a = A(10)
# into __init__
# into __setattr__
# from __init__
print(a.value)
# 10
a.value = 100
# into __setattr__
print(a.value)
# 100
5.__enter__,__exit__
我們知道在操作檔案物件的時候可以這麼寫
with open('a.txt') as f:
'程式碼塊'
上述叫做上下文管理協議,即with語句,為了讓一個物件相容with語句,必須在這個物件的類中宣告__enter__和__exit__方法
__exit__()中的三個引數分別代表異常型別,異常值和追溯資訊,with語句中程式碼塊出現異常,則with後的程式碼都無法執行
如果__exit()返回值為True,那麼異常會被清空,就好像啥都沒發生一樣,with後的語句正常執行
# coding=utf-8
class Open:
def __init__(self, name):
self.name = name
def __enter__(self):
print('出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中程式碼塊執行完畢時執行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with Open('aa.txt') as f:
print(f)
print(f.name)
print(sdsd) # 觸發 __exit__ ,之後的程式碼就不會執行
print('-------')
print('End....')
'''
執行結果:
出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數
<__main__.Open object at 0x0000016D7FA95BE0>
aa.txt
with中程式碼塊執行完畢時執行我啊
<class 'NameError'>
name 'sdsd' is not defined
<traceback object at 0x0000016D7FA925C8>
End....
'''
'''
總結:
__enter__在with語句後自動呼叫,可以給as後的變數賦值
__exit__用於捕獲異常,返回boolean物件,如果為True異常被忽略,如果為False異常被丟擲
三個引數分別是:
1.exc_type 異常型別
2.exc_value異常值
3.traceback
with obj as f:
程式碼塊
1. with obj ----> 觸發obj.__enter__(),拿到返回值
2. as f --> f = 返回值
3. with obj as f 等同於 f = obj.__enter__()
4. 執行程式碼塊
1> 沒有異常,整個程式碼塊執行完畢後去觸發__exit__(),它的三個引數都返回None
2> 有異常的情況下,從異常出現的位置直接觸發__exit))
a:如果__exit__返回值為True,代表吞掉了異常(不報異常)
b:如果__exit__返回值不為True,代表吐出了異常(異常報錯)
c:__exit__執行完畢,就代表整個with語句執行完畢
'''
針對雙下方法的筆試題
1.讓字典具備句點符查詢值的功能
# 1.定義一個類繼承字典
class MyDict(dict):
def __getattr__(self, item):
return self.get(item)
def __setattr__(self, key, value):
self[key] = value
'''要區別是名稱空間的名字還是資料k:v鍵值對'''
obj = MyDict({'name':'jason','age':18})
# 1.具備句點符取v
# print(obj.name)
# print(obj.age)
# 2.具備句點符設k:v
# obj['gender'] = 'male'
obj.pwd = 123 # 給字典名稱空間新增名字 不是資料k:v
print(obj)
2.補全下列程式碼 使其執行不報錯
"""
class Context:
pass
with Context() as ctx:
ctx.do_something()
"""
class Context:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def do_something(self):
pass
with Context() as ctx:
ctx.do_something()
元類
元類就是用於建立類的“東西”。
在理解元類之前,我們必須先掌握Python中的類(class)。
和大多數語言一樣,Python中的類是用來描述如何“生成一個物件”:
class A(object):
pass
a = A()
print(a)
# <__main__.A object at 0x01DFFDD8>
但是,在Python中,類不僅能用來描述如何生成一個物件,類本身也是物件。
在你使用關鍵詞 class 的時候,Python就會執行它,並建立一個物件。
class A(object):
pass
當我們執行這兩行程式碼時在記憶體中建立了一個名為“A”的物件。
這個物件(類)本身具有建立物件(例項)的能力,因此它也是一個類。你可以對它做以下操作:
1.將其分配給變數
2.複製它
3.為其新增屬性
4.將其作為函式引數傳遞
我們可以對A新增屬性等等,還可以對A使用反射
class A(object):
pass
A.name = 1
print(A) # <class '__main__.A'>
print(hasattr(A,'name')) # True
A實際也是一個物件,但它又可以例項出物件。
使用class關鍵字時,Python會幫你自動建立此物件,但是,Python同樣也提供了一種手動建立的方法,那就
是type函式。
type函式最經典的用法是返回物件的型別。但是很少人知道,它還能接受引數並手動建立類。
type(name, bases, attrs)
name: 類名
bases: 元組,父類名
attrs: 字典,類屬性值
A = type('A', (), {})
print(A) # <class '__main__.A'>
class Foo(object):
bar = True
等同於Foo = type('Foo', (), {'bar':True})
繼承
FooChild = type('FooChild', (Foo,), {})
print(FooChild)
# <class '__main__.FooChild'>
print(FooChild.bar)
# True
可見通過 type() 函式建立的類和直接寫class是完全一樣的。
因為Python直譯器遇到class定義時,僅僅是掃描一下class定義的語法,然後呼叫 type() 函式創建出class。
產生類的兩種表現形式(本質是一種)
1.class關鍵字
class C1(object):
pass
print(C1) # <class '__main__.C1'>
2.type元類
type(類名,父類,類的名稱空間)
res = type('C1', (), {})
print(res) # <class '__main__.C1'>
"""
學習元類的目的
元類能夠控制類的建立 也就意味著我們可以高度定製類的行為
eg:掌握了物品的生產過程 就可以在過程中做任何的額外操作
比如:要求類的名字必須首字母大寫
思考在哪裡編寫定製化程式碼
類的產生過程目前還比較懵 元類裡面的__init__方法
物件的產生過程呢 類裡面的__init__方法
方法:由已知推未知
"""
metaclass自定義元類
元類一般用於建立類。在執行類定義時,直譯器必須要知道這個類的正確的元類。直譯器會先尋找類屬性
__metaclass__,如果此屬性存在,就將這個屬性賦值給此類作為它的元類。如果此屬性沒有定義,它會向上查
找父類中的__metaclass__.如果還沒有發現__metaclass__屬性,直譯器會檢查名字為
__metaclass__的全域性變數,如果它存在,就使用它作為元類。否則, 這個類就是一個傳統類,並用
types.ClassType 作為此類的元類。
注意:Python3中不再有__metaclass__屬性以及模組級別的__metaclass__屬性。
在執行類定義的時候,將檢查此類正確的(一般是預設的)元類,元類(通常)傳遞三個引數(到構造器): 類名,
從基類繼承資料的元組,和(類的)屬性字典。
class MyTypeClass(type):
def __init__(cls, cls_name, cls_bases, cls_dict):
# print(cls, cls_name, cls_bases, cls_dict)
if not cls_name.istitle():
raise Exception("類名的首字母必須大寫 你個SD")
super().__init__(cls_name, cls_bases, cls_dict)
class C1(metaclass=MyTypeClass):
school = '清華大學'
class a(metaclass=MyTypeClass):
school = '清華大學'
元類進階操作
1.回想__call__方法
物件加括號會自動執行產生該物件的類裡面的__call__,並且該方法返回什麼物件加括號就會得到什麼
推導:類加括號會執行元類的裡面的__call__該方法返回什麼其實類加括號就會得到什麼
"""類裡面的__init__方法和元類裡面的__call__方法執行的先後順序"""
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
print('__call__ run')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
print('__init__ run')
self.name = name
obj = MyClass('jason')
# 定製物件的產生過程
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
# print('__call__ run')
# print(args,kwargs)
if args:
raise Exception('必須全部採用關鍵字引數')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
# print('__init__ run')
self.name = name
"""強制規定:類在例項化產生物件的時候 物件的獨有資料必須採用關鍵字引數"""
# obj1 = MyClass('jason')
obj2 = MyClass(name='jason')
"""
如果你想高度定製類的產生過程
那麼編寫元類裡面的__init__方法
如果你想高度定製物件的產生過程
那麼編寫元類裡面的__call__方法
"""
__new__
new()方法是在新式類(新式類和經典類的區別)中的方法。object為所有新式類的基類,在object中,
new()方法被定義為靜態方法,並且至少需要傳遞一個引數cls,cls表示需要例項化的類。
在建立一個類物件例項的過程中,new()方法作用在構造方法__init__()之前。new()函式執行後會返回實
例物件(self),然後將self作為第一個引數傳給該類的初始化方法__init__()方法。
可以這麼理解,如果設計一個A類,A類中__new__()方法負責返回一個例項物件(不一定是A類物件),而
__init__()方法負責將類物件初始化(設定各種屬性之類)。在init()呼叫之前,new()可以決定是否要使
用init()方法,因為new()可以呼叫其他類的構造方法或者直接返回別的物件來作為本類(A類)的例項。
#類A
class A(object):
def __init__(self):
self.name = 'A' #設定name屬性
def __new__(cls, *args, **kwargs):
# 執行object的__new__()函式,傳入cls說明返回當前類(A類)的例項物件
return object.__new__(cls)
#類B
class B(object):
def __init__(self):
self.name = 'B' #設定name屬性
def __new__(cls, *args, **kwargs):
#執行object的__new__()函式,傳入引數A說明返回(A類)的例項物件
return object.__new__(A)
a = A() #建立A類例項, 相當於隱式執行了A類的__new__()和__init__()
b = B() #建立B類例項, 只執行了B類的__new__()
print(a.name) # A
#print(b.name) # 出錯:AttributeError: 'A' object has no attribute 'name'
b.__init__( ) #顯示呼叫__init(),完成b的初始化
print(b.name) # A 正常輸出
print(type(a)) # <class '__main__.A'>
print(type(b)) # <class '__main__.A'>
#第一個print(b.name)報錯的原因是,想要建立B類例項的時候,new方法卻直接返回了A類例項物件
#因此,沒有執行B類的init方法,也沒有執行A類的init方法,也就導致b物件沒有name屬性
#在顯示呼叫init方法之後,b才有了name屬性,所以第二個的print(b.name)正常輸出
"""
注意:並不是所有的地方都可以直接呼叫__new__ 該方法過於底層
如果是在元類的__new__裡面 可以直接呼叫
class Meta(type):
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls,*args,**kwargs)
return obj
如果是在元類的__call__裡面 需要間接呼叫
class Mate(type):
def __call__(self, *args, **kwargs):
obj = object.__new__(self) # 建立一個空物件
self.__init__(obj,*args,**kwargs) # 讓物件去初始化
return obj
"""