- isinstance(obj,cls)和issubclass(sub,super)
- 反射 hasattr()、setattr()、getattr()、delattr()
- 反射子產品 __import__和importlib子產品
- __str__和__del__
- __setattr__、__delattr__、__getattr__
- 二次加工标準類型(包裝、授權)
- __getattribute__
- __setitem__和__getitem和__delitem__([]攔截方法)
- __format__(自定義格式化對象的字元串)
- __str__和__repr__
- __del__(析構方法)
- __doc__(類的注釋資訊)
- 描述符(__get__,__set__,__delete__)
- 類的裝飾器
- 通過描述符+裝飾器限制參數類型
- 自制@property、@classmethod、@staticmethod
- __call__
- __init__和__new__(重點)
- __module__和__class__
- __slots__和__dict__
- __all__
- 實作疊代器(__next__和__iter__)
- 上下文管理器(__enter__和__exit__)
- __len__
- __hash__
- __eq__
isinstance和issubclass
isinstance(obj,cls)檢查是否obj是否是類 cls 的對象
ll = [1, 2, 3]
print(isinstance(ll, list))
dic = {'name': 'lqz'}
print(isinstance(dic, dict))
結果:
True
True
isinstance和type的差別:
# isinstance比type強大在,它可以判斷帶有繼承關系的類的對象,type隻能判斷所屬類的對象
class A:
pass
class B(A):
pass
isinstance(A(), A) # returns True
type(A()) == A # returns True
isinstance(B(), A) # returns True
type(B()) == A # returns False
isinstance和type的差別: - type() 不認為子類對象屬于父類的類型,不考慮繼承關系
- isinstance() 認為子類對象屬于父類的類型, 會考慮繼承關系
issubclass(sub, super)檢查sub類是否是 super 類的派生類(子類)
#單層單繼承:
class Mydic(dict):
pass
print(issubclass(Mydic, dict))
print(issubclass(Mydic, list))
print(issubclass(Mydic, object))
結果:
True
False
True
#單層多繼承:是所有父類的派生類
class Foo:
pass
class Mydic(dict, Foo):
pass
print(issubclass(Mydic, dict))
print(issubclass(Mydic, Foo))
結果:
True
True
#多層單繼承:是所有直接或間接父類的派生類
class Animal:
pass
class Dog(Animal):
pass
class TDog(Dog):
pass
print(issubclass(TDog, Dog))
print(issubclass(TDog, Animal))
結果:
True
True
反射
python是動态語言,而反射(reflection)機制被視為動态語言的關鍵。
python面向對象中的反射指在程式的運作狀态中通過字元串的形式操作對象相關的屬性。由于python中的一切事物都是對象,是以都可以使用反射。這種動态擷取程式資訊以及動态調用對象的功能稱為反射機制。首先
通過内置函數dir來
擷取任意一個類或者對象的屬性清單,清單中全為字元串格式
>>> class People:
... def __init__(self,name,age,gender):
... self.name=name
... self.age=age
... self.gender=gender
...
>>> obj=People('egon',18,'male')
>>> dir(obj) # 清單中檢視到的屬性全為字元串 相當于obj.__dir__()
[......,'age', 'gender', 'name']
>>> obj.__dict__ # 屬性字典
{'name': 'egon', 'age': 18, 'gender': 'male'}
接下來就是想辦法
通過字元串來操作對象的屬性了,這就涉及到4個内置函數的使用
四個内置函數(類和對象都可以被這四個函數操作)- hasattr() # 判斷一個對象中是否有某個屬性或方法( 判斷字元串 是不是對象的屬性或方法)
- getattr() # 通過 字元串 擷取對象中的屬性或方法
- setattr() # 通過 字元串 設定對象的屬性或方法
- delattr() # 通過 字元串 删除對象的屬性或方法
class Teacher:
def __init__(self,full_name):
self.full_name =full_name
t=Teacher('Egon Lin')
# hasattr(object,屬性)
hasattr(t,'full_name') # 按字元串'full_name'判斷有無屬性t.full_name
# getattr(object, 屬性, 預設值)
getattr(t,'full_name',None) # 等同于t.full_name,擷取對象中的屬性或方法,不存在該屬性則傳回預設值None
注意:擷取方法是擷取其記憶體位址,使用時注意加括号
# setattr(object, 屬性, 屬性值) # x.y=v
setattr(t,'age',18) # 等同于t.age=18,設定屬性或方法
# delattr(object, 屬性)
delattr(t,'age') # 等同于del t.age,删除了屬性或方法
setattr設定方法使用解析:
class Person:
def __init__(self, name, age):
self.age = age
self.name = name
def print_name(self):
print(self.name)
p = Person('sakura', 18)
def xxx(self):
print('xxx')
print(self.age)
# 如果直接給對象綁定方法
# setattr(p, 'print_age', xxx) #會報錯
# p.print_age()
# 給類綁定方法,綁定給對象的方法是寫在類中,如果使用反射,要反射到類内部
setattr(Person,'print_age',xxx)
p.print_age()
結果:
xxx
18
反射給對象(不推薦使用)
def xxx(obj):
print(obj.age)
setattr(p,'print_age',xxx)
p.print_age(p)
反射子產品 __import__('字元串') # 反射子產品:__import__('字元串')
i = input('請輸入使用哪個子產品:')
# __import__('字元串')
s1 = __import__(i)
s1.test()
print(s1.name)
# 如果是帶路徑的子產品導入
i = input('請輸入使用哪個子產品:') #如 root.db.db_handler
s1 = __import__(i, fromlist=True) # 加上fromlist=True即可
s1.test()
print(s1.name)
反射子產品--importlib子產品 # 反射子產品(py3中用的比較多,架構中)避免了帶有路徑的子產品的導入問題,适用範圍更廣
import importlib
i=input('請輸入您要使用的子產品:')
a=importlib.import_module(i)
a.test()
反射的實際應用案例:
>>> class FtpServer:
... def serve_forever(self):
... while True:
... inp=input('input your cmd>>: ').strip()
... cmd,file=inp.split()
... if hasattr(self,cmd): # 根據使用者輸入的cmd,判斷對象self有無對應的方法屬性
... func=getattr(self,cmd) # 根據字元串cmd,擷取對象self對應的方法屬性
... func(file)
... def get(self,file):
... print('Downloading %s...' %file)
... def put(self,file):
... print('Uploading %s...' %file)
...
>>> server=FtpServer()
>>> server.serve_forever()
input your cmd>>: get a.txt
Downloading a.txt...
input your cmd>>: put a.txt
Uploading a.txt...
反射和隐藏屬性的結合 # 通過反射,擷取隐藏屬性
class Person:
def __init__(self, name, age, sex):
self.age = age
self.sex = sex
self.__name = name
p=Person('lqz',18,'男')
# name=getattr(p,'__name') # 無法擷取__name屬性
name=getattr(p,'_Person__name')
print(name)
# 通過反射,設定成隐藏屬性
class Person:
def __init__(self, name, age, sex):
self.age = age
self.sex = sex
self.__name = name
def print_wife(self):
print(self.__wife) # 相當于self._Person__wife
p=Person('qys',18,'男')
setattr(p,'__wife','劉亦菲')
print(p.__wife) # 能,直接找到__wife屬性
setattr(p,'_Person__wife','劉亦菲') #對對象設定了p._Person__wife='劉亦菲'
# print(p.__wife) # 不能
p.print_wife() # 能
setattr(Person,'_Person__wife','劉亦菲') #對類設定了Person._Person__wife='劉亦菲'
print(Person.__dict__)
print(Person._Person__wife)
p.print_wife()
結果:
劉亦菲
劉亦菲
{'__module__': '__main__'……, '__doc__': None, '_Person__wife': '劉亦菲'}
劉亦菲
劉亦菲
反射的好處 - 實作可插拔機制:可以事先定義好接口,接口隻有在被完成後才會真正執行,這實作了即插即用即你 可以事先把主要的邏輯寫好(隻定義接口),然後後期再去實作接口的功能
- 動态導入子產品(基于反射,反射目前子產品成員)
類的内置方法(面向對象的魔法方法)
Class機制内置了很多特殊的方法,都是
以雙下劃線開頭和結尾的即
魔法方法,會在
滿足某種條件時自動觸發,常用的如下:
__str__和__del__
__str__方法會
在對象被列印時自動觸發,
print功能列印的就是它的傳回值,我們通常基于方法來定制對象的列印資訊,
該方法必須傳回字元串類型>>> class People:
... def __init__(self,name,age):
... self.name=name
... self.age=age
... def __str__(self):
... return '<Name:%s Age:%s>' %(self.name,self.age) #傳回類型必須是字元串
...
>>> p=People('lili',18)
>>> print(p) #觸發p.__str__(),拿到傳回值後進行列印
<Name:lili Age:18>
__del__會在對象被删除時自動觸發 。由于Python自帶的垃圾回收機制會自動清理Python程式的資源,是以當一個對象隻占用應用程式級資源時,完全沒必要為對象定制__del__方法,但
在産生一個對象的同時涉及到申請系統資源(比如系統打開的檔案、網絡連接配接等)的情況下,
關于系統資源的回收,Python的垃圾回收機制便派不上用場了,需要我們為對象定制該方法,用來在對象被删除時自動觸發回收系統資源的操作
class MySQL:
def __init__(self,ip,port):
self.conn=connect(ip,port) # 僞代碼,發起網絡連接配接,需要占用系統資源
def __del__(self):
self.conn.close() # 關閉網絡連接配接,回收系統資源
obj=MySQL('127.0.0.1',3306) # 在對象obj被删除時,自動觸發obj.__del__()
__setattr__和__delattr__和__getattr__(魔法方法之點攔截)
.攔截方法是指通過"
對象.屬性或方法名"這樣的形式去
通路和設定對象的屬性或方法- __setattr__ :在 對象.屬性=值 時會觸發它的執行
- __getattr__ :在 對象.屬性 時擷取值會觸發它的執行 (比較常用,可用于授權)
- __delattr__ :在 del 對象.屬性 時會觸發它的執行
class Foo:
x=1
def __init__(self,y):
self.y=y
def __getattr__(self, item):
print('----> from getattr:你找的屬性不存在')
def __setattr__(self, key, value):
print('----> from setattr')
# self.key=value #這就無限遞歸了,一直觸發__setattr__
# setattr(self,key,value) # 本質同樣是指派,是以會一直觸發對象自己的__setattr__ ,也會遞歸報錯
self.__dict__[key]=value #應該使用它,直接修改屬性字典
object.__setattr__(self, key, value) # 此方式也可以,直接調用object的__setattr__
super().__setattr__(key, value) # 此方式也可以
def __delattr__(self, item):
print('----> from delattr')
# del self.item #無限遞歸了
self.__dict__.pop(item)
#__setattr__添加/修改屬性會觸發它的執行
f1=Foo(10)
print(f1.__dict__) # 因為你重寫了__setattr__,凡是指派操作都會觸發它的運作,除非你直接操作屬性字典,不會觸發它的執行
f1.z=3
print(f1.__dict__)
f1.__dict__['a']=3 #我們可以直接修改屬性字典,來完成添加/修改屬性的操作,不會觸發__setattr__
print(f1.__dict__)
#__delattr__删除屬性的時候會觸發
del f1.a
print(f1.__dict__)
#__getattr__隻有在使用點調用屬性且屬性不存在的時候才會觸發
f1.xxxxxx
結果:
----> from setattr #生成對象時的指派
{'y': 10}
----> from setattr #派生屬性的指派
{'y': 10, 'z': 3}
{'y': 10, 'z': 3, 'a': 3} #直接修改屬性字典,不會觸發__setattr__的執行
----> from delattr #删除屬性
{'y': 10, 'z': 3}
----> from getattr:你找的屬性不存在 #查找屬性且屬性不存在
關于 object.__setattr__(self, key, value) 的分析:(多次試驗後可知所有内置的類和object一樣都可以避免遞歸,,但object容易了解) 由于object中的__setattr__具有特殊性,一般對象在不觸發自己__setattr__的情況下,即運作了object中的__setattr__是不會出現遞歸現象的,是以可以利用這個特性,
直接調用object中的__setattr__進行指派就可以了,避免了再次觸發__setattr__ 後期封裝(隐藏)+setattr的結合使用__setattr__(self, key, value)
對象.__dict__[_類名__屬性名]='qys' #指派時變形封裝
類内部使用 self.__屬性名 #内部不需要變形
類外部使用: 對象._類名__屬性名 #外部統一隻能變形取值,主要目的就是隐藏屬性
二次加工标準類型(在原有基礎上擴充, 基于繼承實作 )
包裝在很多場景下我們都需要
基于标準資料類型來定制我們自己的資料類型,
新增/改寫方法,這也可以成為包裝示例:加工list類
class List(list): #繼承list所有的屬性,也可以派生出自己新的功能或屬性,比如append和mid
def append(self, p_object):
'派生自己的append:加上類型檢查'
if not isinstance(p_object,int): #判斷p_object是不是屬于int類的
raise TypeError('must be int') #報異常
super().append(p_object)
@property #将mid僞裝成屬性
def mid(self):
'新增自己的屬性'
index=len(self)//2
return self[index]
l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111') #報錯,必須為int類型
print(l.mid)
#其餘的方法都繼承list的
l.insert(0,-123)
print(l)
l.clear()
print(l)
授權 授權是包裝的一個特性 , 包裝一個類型通常是對已存在的類型的一些定制,這種做法可以建立,修改或删除原有産品的功能。其它的則保持原樣。
授權的過程,則是所有更新的功能都是由新類的某部分來處理,但已存在的功能就直接授權給對象,變為對象的預設屬性。
實作授權的關鍵點就是覆寫__getattr__方法示例:
import time
class FileHandle:
def __init__(self,filename,mode='r',encoding='utf-8'):
self.file=open(filename,mode,encoding=encoding)
def write(self,line): #更新了write功能
t=time.strftime('%Y-%m-%d %T')
self.file.write('%s %s' %(t,line))
def __getattr__(self, item):
return getattr(self.file,item) # object.屬性,self.file相當于檔案句柄
f1=FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0) # 指針移動至開頭,都是直接用的預設屬性
print(f1.read())
f1.close()
__getattribute__
# 無論屬性在對象中是否存在,都先觸發__getattribute__的執行(用的比較少)
class Person:
name = 'lqz'
def __getattr__(self, item): # 屬性不存在,才觸發
print('getattr觸發了')
def __getattribute__(self, item): # 無論屬性在對象中是否存在,都先觸發
print('__getattribute__觸發了')
p = Person()
print(p.name)
# 對象.屬性 調用順序 先執行:__getattribute__----》類的名稱空間----》__getattr__(本質是去對象自己的名稱空間拿東西)
# 與對象.屬性的查找順序不一樣
#當getattribute與getattr同時存在,隻會執行getattrbute,除非getattribute在執行過程中抛出異常AttributeError
__setitem__和__getitem和__delitem__([]攔截方法)
成功實作了通過"
對象['屬性或方法名']"這樣的字元串形式
去通路和設定對象的屬性或方法- __getitem__:通過中括号取值觸發它的執行
- __setitem__:通過中括号指派,觸發它的執行
- __delitem__:通過中括号删除值,觸發它的執行
示例:
class Foo:
def __init__(self, name):
self.name = name
def __getitem__(self, item):
return (self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key] = value
def __delitem__(self, key):
print('del obj[key]時,我執行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key時,我執行')
self.__dict__.pop(item)
f1 = Foo('qys')
f1['age'] = 18
f1['age1'] = 19
del f1.age1
del f1['age']
f1['name'] = 'hhh'
print(f1.__dict__)
print(f1['name'])
結果:
del obj.key時,我執行
del obj[key]時,我執行
{'name': 'hhh'}
hhh
__format__(自定義格式化對象的字元串)
示例:
class Person():
__dic = {
'n-a': '名字是:{obj.name}-----年齡是:{obj.age}', # 名字是:qys-年齡是:18
'n:a': '名字是:{obj.name}:::::年齡是:{obj.age}', # 名字是:qys:年齡是:18
'n/a': '名字是:{obj.name}/年齡是:{obj.age}', # 名字是:qys/年齡是:18
}
def __init__(self, name, age, sex):
self.name = name
self.sex = sex
self.age = age
def __format__(self, format_spec):
print(format_spec)
if format_spec and format_spec in self.__dic:
s = self.__dic[format_spec]
else:
s = self.__dic['n-a']
return s.format(obj=self)
p = Person('qys', 18, '男')
# specification為指定格式,當應用程式中出現"{0:specification}".format(someobject)或format(someobject, specification)時,會預設以這種方式調用
# 第一種觸發它執行
res = format(p)
print(res)
res = format(p, 'n-a')
print(res)
res = format(p, 'n/a')
print(res)
res = format(p, 'n:a')
print(res)
# 第二種觸發方式
print('{:n-a}'.format(p))
print('{0:n-a}'.format(p))
結果:
名字是:qys-----年齡是:18
n-a
名字是:qys-----年齡是:18
n/a
名字是:qys/年齡是:18
n:a
名字是:qys:::::年齡是:18
n-a
名字是:qys-----年齡是:18
n-a
名字是:qys-----年齡是:18
注意:"{0!s}".format()和"{0!r}".format()并不會調用__format__()方法,他們會直接調用__str__()或者__repr__() __str__和__repr__
改變對象的字元串顯示- __str__ : print(對象) 觸發對象的 __str__
- __repr__: 在 指令視窗即互動式 中直接寫對象名 觸發 __repr__
class Person:
def __init__(self, name):
self.name = name
def __str__(self): # 會經常寫
return self.name + 'ddddd'
def __repr__(self): # 寫的少
return self.name
#如果__str__沒有被定義,那麼就會使用__repr__來代替輸出,這倆方法的傳回值必須是字元串,否則抛出異常
p = Person('qys')
print(p)
結果:
qysddddd
__del__(析構方法)
析構方法,當對象在記憶體中被釋放時,自動觸發執行。(析構方法: 與構造方法正好相反,構造方法用于執行個體化一個對象,析構方法用于清理一個對象,垃圾回收)如果産生的對象僅僅隻是python程式級别的(使用者級),那麼無需定義__del__,
如果産生的對象的同時還會向作業系統發起系統調用,即一個對象有使用者級與核心級兩種資源,比如(打開一個檔案,建立一個資料庫連結),則必須在清除對象的同時回收系統資源,這就用到了__del__
應用場景:- 建立 資料庫類 ,用該類執行個體化出資料庫連結對象,對象本身是存放于使用者空間記憶體中,而連結則是由作業系統管理的,存放于核心空間記憶體中
- 當程式結束時,python隻會回收自己的記憶體空間,即使用者态記憶體,而作業系統的資源則沒有被回收,這就需要我們定制__del__,在對象被删除前向作業系統發起關閉資料庫連結的系統調用,回收資源 (與檔案處理類似)
示例:檔案處理(with上下文管理)
class ProcessFile:
def __init__(self, name):
self.f = open(name, 'wt', encoding='utf-8')
def write(self, s):
self.f.write(s)
def close(self):
self.f.close()
def __del__(self):
# 資源清理工作
self.f.close()
print('運作了__del__')
p = ProcessFile('a.txt')
p.write('xxxx')
p.close()
del p
結果:
運作了__del__
__doc__(類的注釋資訊)
注意:繼承關系下,類的注釋不能繼承class Animal:
'''
Animal的注釋,動物類,所有屬于動物的都繼承我
'''
class Person(Animal):
pass
print(Animal.__doc__)
print(Person.__doc__) #注釋不會繼承
結果:
Animal的注釋,動物類,所有屬于動物的都繼承我
None
描述符(__get__,__set__,__delete__)
描述符本質就是一個新式類,在這個新式類中,
至少實作了__get__(),__set__(),__delete__()中的一個,這個類也被稱為
描述符協定- __get__():調用一個屬性時,觸發 #類.屬性
- __set__():為一個屬性指派時,觸發 #類.屬性=值
- __delete__():采用del删除屬性時,觸發 #del 類.屬性
class Foo: #在python3中Foo是新式類,它實作了三種方法,這個類就被稱作一個描述符
def __get__(self, instance, owner):
print('觸發get')
def __set__(self, instance, value):
print('觸發set')
def __delete__(self, instance):
print('觸發delete')
#包含這三個方法的新式類稱為描述符,由這個類産生的執行個體進行屬性的調用/指派/删除,并不會觸發這三個方法
f1=Foo()
f1.name='egon'
f1.name
del f1.name
運作結果:
無任何内容
描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成目标類的類屬性,不能定義到構造函數中) 示例:
# 描述符Str
class Str:
def __get__(self, instance, owner):
print('Str調用')
def __set__(self, instance, value):
print('Str設定...')
def __delete__(self, instance):
print('Str删除...')
# 描述符Int
class Int:
def __get__(self, instance, owner):
print('Int調用')
def __set__(self, instance, value):
print('Int設定...')
def __delete__(self, instance):
print('Int删除...')
class People:
name = Str() # name被Str類代理
age = Int() # age被Int類代理
類代理
def __init__(self, name, age): # name被Str類代理,age被Int類代理,
self.name = name # 指派,就會觸發描述符類的__set__方法
self.age = age
p1 = People('alex', 18)
# 描述符Str的使用
p1.name
p1.name = 'egon'
del p1.name
# 描述符Int的使用
p1.age
p1.age = 18
del p1.age
print(p1.__dict__)
print(People.__dict__) # 描述符隻代理類屬性,不代理對象屬性
print(type(p1) == People) # p1就是由People類執行個體化來的
結果:
Str設定...
Int設定...
Str調用
Str設定...
Str删除...
Int調用
Int設定...
Int删除...
{} #對象中就沒有屬性,隻在類中有
{'__module__': '__main__', 'name': <__main__.Str object at 0x0000023E8C925610>, 'age': <__main__.Int object at 0x0000023E8C925BE0>,…… '__doc__': None}
True
#
隻要類屬性被描述符類代理了,隻要符合觸發條件 ,就會觸發描述符類的__set__,__get__,__delete__,并且
隻在類名稱空間有,對象名稱空間就沒有該屬性了 描述符種類 ①資料描述符:至少實作了__get__()和__set__()class Str:
def __get__(self, instance, owner):
pass
def __set__(self, instance, value): #self指描述符的對象,instance指被代理類的對象,value指所賦的值
pass
def __delete__(self, instance):
pass
②非資料描述符:沒有實作__set__() class Str:
def __get__(self, instance, owner):
pass
描述符注意事項 - 描述符本身 應該定義成 新式類 , 被代理的類 也應該是 新式類
- 必須把描述符定義成這個類的類屬性 ,不能為定義到構造函數中(如果放在了init方法中,那麼描述符代理将失效,隻有是類屬性描述符才會代理)
- 調用要嚴格 遵循該優先級 ,優先級由高到底分别是
- 類屬性
- 資料描述符
- 執行個體屬性 (對象屬性)
- 非資料描述符(沒有__set__即沒有設定修改屬性值)
- 找不到以上屬性則觸發getattr()
ps:
函數就是一個由非描述符類執行個體化得到的對象 描述符使用衆所周知,python是強類型動态語言,即參數的指派沒有類型限制,下面我們
通過描述符機制來實作類型限制功能 ps:- 強類型表示不同類型直接不允許直接運算,python中不允許直接運算,類型轉換後可操作,js中允許直接運算,java:隐式類型轉換,go語言強類型
- 動态語言一般為解釋型如:js,php,python,不使用顯示資料類型聲明,聲明變量不需要指定變量類型,靜态語言一般為編譯型如:java,c,go
# 通過描述符限制參數類型
class Typed:
def __init__(self,key,expected_type):
self.key=key
self.expected_type=expected_type
def __get__(self, instance, owner):
print('get--->',instance,owner)
if instance is None:
return self
return instance.__dict__[self.key]
def __set__(self, instance, value):
print('set--->',instance,value)
if not isinstance(value,self.expected_type): #判斷類型是否符合要求
raise TypeError('Expected %s' %str(self.expected_type))
instance.__dict__[self.key]=value # 将key,value放入到目标類對象的屬性字典中
def __delete__(self, instance):
print('delete--->',instance)
instance.__dict__.pop(self.key)
class People:
name=Typed('name',str)
age=Typed('age',int)
salary=Typed('salary',float)
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
# p1=People(123,18,3333.3) #錯誤
# p1=People('qys','18',3333.3) #錯誤
p1=People('qys',18,3333.3)
print(p1.__dict__)
結果:
set---> <__main__.People object at 0x0000022337C4C040> qys
set---> <__main__.People object at 0x0000022337C4C040> 18
set---> <__main__.People object at 0x0000022337C4C040> 3333.3
{'name': 'qys', 'age': 18, 'salary': 3333.3}
類的裝飾器
文法糖@的作用是對類進行裝飾,不影響類的其他代碼的執行 類的裝飾器:無參def decorate(cls):
print('類的裝飾器開始運作啦------>')
return cls
@decorate #無參:People=decorate(People)
class People:
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
p1=People('qys',18,3333.3)
類的裝飾器:有參 def typeassert(**kwargs):
def decorate(cls):
print('類的裝飾器開始運作啦------>',kwargs)
return cls
return decorate
@typeassert(name=str,age=int,salary=float) #有參:1.運作typeassert(...)傳回結果是decorate,此時參數都傳給kwargs 2.People=decorate(People)
class People:
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
p1=People('qys',18,3333.3)
通過描述符+裝飾器限制參數類型 class Typed:
def __init__(self,name,expected_type):
self.name=name # 如name.name=name 當self為name
self.expected_type=expected_type
def __get__(self, instance, owner):
print('get--->',instance,owner)
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('set--->',instance,value)
if not isinstance(value,self.expected_type):
raise TypeError('Expected %s' %str(self.expected_type)) #報異常
instance.__dict__[self.name]=value #寫入對象的屬性字典
def __delete__(self, instance):
print('delete--->',instance)
instance.__dict__.pop(self.name)
def typeassert(**kwargs): #用于設定描述符對象
def decorate(cls):
print('類的裝飾器開始運作啦------>',kwargs)
for name,expected_type in kwargs.items():
setattr(cls,name,Typed(name,expected_type)) #生成描述符對象,等同于name=Typed(name,expected_type)
return cls
return decorate
@typeassert(name=str,age=int,salary=float) #有參:1.運作typeassert(...)傳回結果是decorate,此時參數都傳給kwargs 2.People=decorate(People)
class People:
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
print(People.__dict__)
p1=People('egon',18,3333.3)
描述符是可以實作大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性 描述符是很多進階庫和架構的重要工具之一,描述符通常是使用到裝飾器或者元類的大型架構中的一個元件 自制 @property
class Lazyproperty:
def __init__(self,func):
self.func=func # area.func=area,area為Lazyproperty的對象
def __get__(self, instance, owner):
print('這是我們自己定制的靜态屬性,r1.area實際是要執行r1.area()')
if instance is None:
return self #如果沒有對象則傳回area
return self.func(instance) # area(r1),普通函數需要傳對象
class Room:
def __init__(self,name,width,length):
self.name=name
self.width=width
self.length=length
@Lazyproperty #area=Lazyproperty(area) 相當于定義了一個類屬性,即描述符
def area(self):
return self.width * self.length
r1=Room('alex',2,4)
print(r1.area) # r1.area是Lazyproperty的對象,類是描述符類---->r1.area會觸發描述符類的 __get__方法
緩存起來 class Lazyproperty:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner):
print('這是我們自己定制的靜态屬性,r1.area實際是要執行r1.area()')
if instance is None:
return self
else:
print('--->')
value=self.func(instance) # 算出值賦給value
setattr(instance,self.func.__name__,value) #計算一次就緩存到執行個體r1的屬性字典中
#相當于instance.__dict__[self.func.__name__]=value
return value
class Room:
def __init__(self,name,width,length):
self.name=name
self.width=width
self.length=length
@Lazyproperty #area=Lazyproperty(area) 相當于'定義了一個類屬性,即描述符'
def area(self):
return self.width * self.length
r1=Room('alex',1,1)
print(r1.area) #先從自己的屬性字典找,沒有再去類的中找,然後執行了area的__get__方法
print(r1.area) #先從自己的屬性字典找,找到了,是上次計算的結果,這樣就不用每次都執行__get__去計算
注意:前提是確定Lazyproperty是非資料描述符,否則按照優先級會先找資料描述符,緩存功能就失效了 自定制@classmethod
#設定綁定到類的方法
class ClassMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,執行個體來調用,instance為執行個體,owner為類本身,
def feedback():
print('在這裡可以加功能啊...')
return self.func(owner) #確定傳入的是類即可
return feedback
class People:
name='linhaifeng'
@ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls):
print('你好啊,帥哥 %s' %cls.name)
People.say_hi()
p1=People()
p1.say_hi() #還是傳類People
#類方法如果有參數
class ClassMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,執行個體來調用,instance為執行個體,owner為類本身,
def feedback(*args,**kwargs): #接收所有參數
print('在這裡可以加功能啊...')
return self.func(owner,*args,**kwargs)
return feedback
class People:
name='linhaifeng'
@ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls,msg):
print('你好啊,帥哥 %s %s' %(cls.name,msg))
People.say_hi('你是那偷心的賊')
p1=People()
p1.say_hi('你是那偷心的賊')
自定制的@staticmethod
# 設定非綁定方法,類和對象都可調用,無需加self或cls
class StaticMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,執行個體來調用,instance為執行個體,owner為類本身,
def feedback(*args,**kwargs):
print('在這裡可以加功能啊...')
return self.func(*args,**kwargs)
return feedback
class People:
@StaticMethod# say_hi=StaticMethod(say_hi)
def say_hi(x,y,z):
print('------>',x,y,z)
People.say_hi(1,2,3)
p1=People()
p1.say_hi(4,5,6)
補充:字典,清單等為什麼不能直接進行點指派
字典:
dic = {'name': 'lqz'}
dic.p = 'ppp' # 觸發dict類的__setattr__ 直接raise異常,不允許這樣指派,但函數可以點指派,這和所屬類的底層代碼有關,無需深入了解
函數:
def test():
pass
print(type(test))
test.name = 'qys'
print(test.__name__)
print(test.name)
結果:
<class 'function'>
test
qys
__call__
對象後面加括号,觸發執行----對象()注:構造方法的執行是由建立對象觸發的,即:對象 = 類名() ;而對于 __call__ 方法的執行是由對象後加括号觸發的,即:對象() 或者 類()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 執行 Foo的__init__
obj() # 執行 Foo的__call__
由于一切皆對象,so
# Foo 類,也是一個對象 Foo()----->觸發生成Foo類的類的__call__,不是觸發Foo的__call__
# Foo類生成的對象 --->obj()--->觸發Foo的__call__
__init__和__new__(重點)
- __init__ 創造完空對象後,再觸發它,完成初始化 ,類() 會觸發
- __new__ 對象生成,創造出一個空對象,沒有初始化, 類() 會觸發,且是在__init__之前觸發
class Person:
def __init__(self, name):
print('我執行了,我是__init__')
self.name = name
def __new__(cls, *args, **kwargs):
print('我出生了')
obj = object.__new__(cls)
return obj # 觸發 __init__
p = Person('qys') # 觸發__new__,傳回 obj 對象給p
print(p.name)
結果:
我出生了
我執行了,我是__init__
qys
總結:類執行個體化得到對象,本質是先執行類的 __new__,傳回什麼對象,p拿到的就是什麼對象,在__new__内部,調用了__init__即__init__是初始化方法,在__new__後執行的 __module__和__class__
- __module__ 表示目前操作的對象在那個子產品
- __class__ 表示目前操作的對象的類是什麼
aa檔案内容:
class C:
def __init__(self):
self.name = 'SB'
執行檔案:
from aa import C
obj = C()
print(obj.__module__) # 輸出所屬子產品
print(obj.__class__) # 輸出所屬類
結果:
aa
<class 'aa.C'>
__slots__和__dict__
__slots__是一個類變量,
變量值可以是清單,元祖,或者可疊代對象,也可以是一個字元串(意味着所有執行個體隻有一個資料屬性),使用點來通路屬性本質就是在通路類或者對象的__dict__屬性字典
(類的字典是共享的,而每個執行個體的是獨立的) 使用__slots__的目的:字典會占用大量記憶體
,如果你有一個屬性很少的類,但是有很多執行個體,為了節省記憶體可以使用__slots__取代執行個體的__dict__
當你定義__slots__後,__slots__就會為執行個體使用一種更加緊湊的内部表示。執行個體通過一個很小的固定大小的數組來建構,而不是為每個執行個體定義一個字典,這跟元組或清單很類似。在__slots__中列出的屬性名在内部被映射到這個數組的指定小标上。
使用__slots__的缺點就是我們不能再給執行個體添加新的屬性了,隻能使用在__slots__中定義的那些屬性名。 注意事項:- __slots__的很多特性都依賴于普通的基于字典的實作。另外, 定義了__slots__後的類不再支援一些普通類特性了,比如多繼承 。大多數情況下,你應該 隻在那些經常被使用到的用作資料結構的類上定義__slots__ 比如在程式中需要建立某個類的幾百萬個執行個體對象 。
- __slots__的一個常見誤區是它 可以作為一個封裝工具來防止使用者給執行個體增加新的屬性 。盡管使用__slots__可以達到這樣的目的, 但是這個并不是它的初衷,更多的是用來作為一個記憶體優化工具。
class Person:
__slots__ = ['name', 'age'] # 對象隻能擁有name和age屬性,使用了slots後,所有的對象,都使用類名稱空間中的屬性,對象越多,越節省記憶體
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name
p1 = Person('qys', 19)
p2 = Person('sakura', 18)
# print(p1.__dict__) 報錯(對象自己沒有名稱空間了,統一全用類的名稱空間)
# print(p2.__dict__) 報錯(一旦使用了slots,對象就沒有自己的dict了)
print(p1.__slots__)
print(p2.__slots__)
print(Person.__dict__)
print(p1.name)
print(p2.name)
結果:
['name', 'age']
['name', 'age']
{'__module__': '__main__'…… 'age': <member 'age' of 'Person' objects>, 'name': <member 'name' of 'Person' objects>, '__doc__': None}
qys
sakura
__all__
當使用from aa import * 導入子產品時,
可以限制能夠使用的屬性aa.py檔案内容:
__all__ = ['age', 'name']
age = 10
name = 'lqz'
sex = '男'
執行程式:
# __all__:當使用from aa import * 導入子產品時,可以限制能夠使用的屬性
from aa import *
print(age)
print(name)
# print(sex) #報錯
實作疊代器(__next__和__iter__)
補充:python中的
for循環,
是依賴于疊代的循環,不依賴于索引
python中for循環的本質:for i in 可疊代對象:會調用可疊代對象的__iter__,得到一個疊代器對象,不停的調用對象.__next__,得到一個值,直到抛出異常,for循環結束- 可疊代對象:對象有__iter__方法的,就是可疊代對象,調用__iter__,得到疊代器對象
- 疊代器對象:既有__iter__又有__next__對象,就是疊代器對象
形如:f.test().test2().test3() # 在java,js中廣泛應用,設計程式的模式
示例:
class Foo:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def print_name(self):
print(self.name)
return self # 傳回對象本身即可完成鍊式調用
def print_age(self):
print(self.age)
return self # 傳回對象本身即可完成鍊式調用
def print_sex(self):
print(self.sex)
return self # 傳回對象本身即可完成鍊式調用
f = Foo('qys', 16, '男')
f.print_name()
f.print_sex()
f.print_age()
f.print_name().print_sex().print_age()
結果:
qys
男
16
qys
男
16
__next__和__iter__必須配合for循環一起使用
簡單示例:對象可疊代,循環累加1
class Foo:
def __init__(self, x):
self.x = x
def __iter__(self):
return self
def __next__(self):
self.x += 1
return self.x
f = Foo(6) # f有__iter__方法,是以就是一個可疊代對象,是以f就能夠被for循環
for i in f: #先運作__iter__,然後循環執行__next__并将傳回值指派給i,直至報StopIteration異常
print(i)
自制range功能(帶步長)
class Range:
def __init__(self, start, stop, step):
self.start = start
self.stop = stop
self.step = step
def __next__(self):
if self.start >= self.stop:
raise StopIteration
x = self.start
self.start += self.step
return x
def __iter__(self):
return self
for i in Range(1, 7, 3):
print(i)
結果:
1
4
實作斐波那契數列
class Fib:
def __init__(self):
self._a = 0 # 确定初始的兩個值
self._b = 1
def __iter__(self):
return self
def __next__(self):
self._a, self._b = self._b, self._a + self._b
return self._a
f1 = Fib()
for i in f1:
if i > 100:
break
print(i, end=' ')
結果:
1 1 2 3 5 8 13 21 34 55 89
上下文管理器(__enter__和__exit__)
操作檔案對象的時候可以這麼寫
with open('a.txt') as f:
f.read()
這就是上下文管理協定,即with語句 ,為了讓一個對象相容with語句,
必須在這個對象的類中聲明__enter__和__exit__方法__exit__()中的
三個參數分别代表異常類型,異常值和追溯資訊,with語句中代碼塊出現異常,則with後的代碼都無法執行,
如果__exit()傳回值為True,那麼異常會被清空,就好像啥都沒發生一樣,
with後的語句正常執行 出現with語句,對象的__enter__被觸發,有傳回值則指派給as聲明的變量,with中代碼塊執行完畢時執行__exit__class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出現with語句,對象的__enter__被觸發,有傳回值則指派給as聲明的變量,無傳回值則預設為None')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代碼塊執行完畢時執行我啊')
print(exc_type) #異常類型
print(exc_val) #異常值
print(exc_tb) #追溯資訊
return True #即使報錯,也會繼續正常執行後續代碼
with Open('a.txt') as f:
print('=====>執行代碼塊')
print(f)
raise AttributeError('***着火啦,救火啊***') #報異常
print('0'*20)
結果:
出現with語句,對象的__enter__被觸發,有傳回值則指派給as聲明的變量
=====>執行代碼塊
None
with中代碼塊執行完畢時執行我啊
<class 'AttributeError'>
***着火啦,救火啊***
<traceback object at 0x000002620F4CA680>
00000000000000000000
好處: - 使用with語句的目的就是把代碼塊放入with中執行,with結束後,自動完成清理工作,無須手動幹預
- 在需要管理一些資源比如檔案,網絡連接配接和鎖的程式設計環境中, 可以在__exit__中定制自動釋放資源的機制 ,你無須再去關心這個問題,這将大有用處,後期會使用with ,處理資料庫連結,redis連結
class Open:
def __init__(self, filepath, mode='r', encoding='utf-8'):
self.filepath = filepath
self.mode = mode
self.encoding = encoding
def __enter__(self):
self.f = open(self.filepath, mode=self.mode, encoding=self.encoding) #打開檔案
return self.f # self.f作為檔案句柄指派給f
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close() #關閉檔案
print('異常資訊:',exc_val)
return True
with Open('a.txt', 'w') as f:
print(f)
f.write('aaaaaa')
f.aaaa #會報異常
print('即使異常也會繼續運作')
結果:
<_io.TextIOWrapper name='a.txt' mode='w' encoding='utf-8'>
異常資訊: '_io.TextIOWrapper' object has no attribute 'aaaa'
即使異常也會繼續運作
__len__
__len__:計算對象長度,len(對象)就會觸發__len__并運作
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def __len__(self):
return len(self.__dict__)
f = Foo('lqz', 19)
f.hobby = 'xx'
print(len(f))
結果:
3
__hash__
__hash__:hash(對象)觸發執行,傳回什麼就是什麼,作用是如果一個對象不可hash,通過重寫 __hash__讓它變得可hash
python中可變類型,不可hash;不可變類型,可以hashclass Foo:
def __init__(self):
self.name = 'lqz'
self.l = ['1', 3, 4]
def __hash__(self):
# return hash(self.l) # 報錯,因為l是清單,不可hash
return hash(self.name)
f = Foo()
print(hash(f))
結果:
1701692016635061810
__eq__
__eq__:兩個對象
== 比較的時候,觸發 __eq__的執行,在内部自定制比較規則即可
ps:==比較的隻是值value,是以不同類型之間也可以互相比較,is比較的是id号class Foo:
def __str__(self):
return self.name
def __init__(self):
self.name = 'qys'
def __eq__(self, other):
print(self)
print(other)
# 可自己定制比較規則
if self.name == other.name:
return True
else:
return False
f1 = Foo()
f1.name = 'sakura'
f1.age = 999
f2 = Foo()
f2.age = 888
print(f1 == f2) # 觸發__eq__,f1為self,f2為other
print(f1 is f2)
結果:
sakura
qys
False
False