Python高級編程和異步IO並發編程
一、類與對象
1、抽象基類(abc模塊)
# 可判斷某類是否有某方法 hasattr(obj, '__len__') # 我們在某些情況之下希望判定某個對象的類型 isinstance(obj, list) # 我們需要強制某個子類必須實現某些方法 # 如:實現了一個web框架,集合cache(如希望可替換成 redis, cache, memorychache 之一) # 則需要設計一個抽象基類,指定子類必須實現某些方法 # 如何去模擬一個抽象基類: class CacheBase(): def get(self, key): raise NotImplementedError #子類未重載該方法時,調用該方法會拋出異常 def set(set, key, value): raise NotImplementedError class RedisCache(CacheBase): pass redis_cache = RedisCache() # 調用時會拋出異常 redis_cache.set('key', 'value') # 如果我們希望初始化時就拋出異常: import abc class CacheBase(metaclss=abc.ABCMeta): @abc.abstractmethod def get(self, key) pass @abc.abstractmethod def set(set, key, value): raise NotImplementedError class RedisCache(CacheBase): pass # 初始化時會拋出異常 redis_cache = RedisCache()
2、isinstance 與 type 區別
class A: pass class B(A): pass b = B() print(isinstance(b, B)) # 返回:True print(isinstance(b, A)) # 返回:True print(type(b)) # 返回:<class '__main__.B'> print(type(b) is B) # 返回:True # type 無法找出繼承關系 print(type(b) is A) # 返回:False
因此,類型判斷一般使用isinstance ,少用type
3、類變量 和 對象變量
類變量:類 及 對象均可以調用,不同點在於:類調用時如果修改該變量數據,則在該類中數據被永久修改;對象在調用該變量時,如果是修改該變量,此時修改的是自己對象中該變量的值,不會影響到類中該變量的值
對象變量:類不能調用,只能對象可以調用
私有屬性/方法:在類當中定義私有屬性/方法 ,只能在類內使用,不能在類之外被點語法等調用。私有屬性(__name)在python內部中實際是是被結構化處理成:_classname__name,在類外層調用,不能直接調用__name屬性,但可以調用
_classname__name 來獲取私有屬性的值,其中classname是類的名稱
4、super函數
class A: def __init__(self): print('A') class B: def __init__(self): print('B') super().__init__() class C(A): def __init__(self): print('C') super().__init__() class D(B, C): def __init__(self): print('D') super(D, self).__init__() if __name__ == '__main__': print(D.__mro__) # 返回:(<class '__main__.D'>,<class '__main__.B'>,<class '__main__.C'>,<class '__main__.A'>,<class 'object'>) d = D() # 返回: # D # B # C # A
super函數的調用,不是調用父類的放法,而是按MRO算法順序調用的,具體參考上述代碼
5、上下文管理器 with語句
with 上下文管理器有兩個魔法函數:
- __enter__ :程序啟動時調用
- __exit__ :程序結束時調用
# 上下文管理器協議 class Sample(): def __enter__(self): print('enter') #作用獲取資源,自動調用 return self def __exit__(self, exc_type, exc_val, exc_tb): #作用釋放資源,自動調用 print('exit') def do_something(self): print('doing something') with Sample() as sample: sample.do_something() # 返回: # enter # doing something # exit
python 內置模塊:contextlib 簡化上下文管理器:
import contextlib @contextlib.contextmanager # 加上contextlib裝飾器,將file_open函數變成上下文管理器 def file_open(file_name): # yield 前相當於 __enter__,yield 后相當於 __exit__ print('file open') yield {} print('file end') with file_open('filename.txt') as f_opened: print('file processing') # 返回: # file open # file processing # file end
二、自定義序列類
1、序列類型的分類:
- 容器序列:list、tuple、deque --可放置任何類型
- 扁平序列:str、bytes、bytearray、array.array --可遍歷的、放置的數據類型需相同
- 可變序列:list、deque、bytearray、array
- 不可變序列:str、tuple、bytes
append、extend區別:
append:將數據原原整整追加到對象中,如a=[1,2],a.append((3,4)),結果:[1,2,(3,4)]
extend:將數據追加到對象中,以自身類型為准。如a=[1,2],a.extend([3,4]),結果:[1,2,3,4]
2、實現可切片的對象
import numbers class Group: def __init__(self, group_name, company_name, staffs) self.group_name = group_name self.company_name = company_name self.staffs = staffs def __reversed__(self): # 實現數據反轉功能 self.staffs.reverse() def __getitem__(self, item): # 實現可切片功能 cls = type(self) if isinstance(item, slice): return cls(group_name=self.group_name, company_name=self.company_name, staffs=self.staffs[item]) else isinstance(item, numbers.Integral): return cls(group_name=self.group_name, company_name=self.company_name, staffs=[self.staffs[item]]) def __len__(self): # 實現len()查看長度功能 return len(self.staffs) def __iter__(self): # 實現可迭代功能 return iter(self.staffs) def __contains__(self, item): # 實現if判斷功能 if item in self.staffs: return True else: return False staffs = ['MJ1','MJ2','MJ3','MJ4'] group = Group(company_name='i', group_name='user', staffs=staffs) sub_group = group[:2] # 調用getitem方法 len(group) # 調用__len__方法 for i in group : # 調用__iter__方法 if sb in group: # 調用 __contains__方法 ### 通過實現相關的魔法函數,可以使自定義的類具有某些功能 ###
3、bisect 維護已排序的序列
import bisect # 用來處理已排序的序列,用來維持已排序的序列,升序 # 內部使用的是二分查找法 inter_list = deque() bisect.insort(inter_list, 3) # 插入數據,默認在右邊插入 bisect.insort(inter_list, 2) bisect.insort(inter_list, 5) bisect.insort(inter_list, 1) bisect.insort(inter_list, 6) print(bisect.bisect(inter_list, 3)) # 查找數據位置, 返回:3 print(bisect.bisect_left(inter_list, 3)) # 返回:2 print(inter_list) # 返回:[1,2,3,5,6]
4、列表推導式、生成器表達式、字典推導式
- 列表推導式性能高於列表操作
列表生成式(列表推導式):
# 1. 提取出1~20之間的奇數 odd_list = [] for i in range(21): if i%2 == 1: odd_list.append(i) print(odd_list) # 列表推導式: odd_list = [i for i in range(21) if i % 2 == 1] print(odd_list) # 2. 邏輯復雜的情況 def hadle_item(item): return item * item odd_list = [hadle_item(i) for i in range(21) if i % 2 == 1] print(type(odd_list)) print(odd_list)
生成器表達式:
# 生成器表達式 odd_gen = (i for i in range(21) if i % 2 == 1) print(type(odd_gen)) # <class 'generator'>
生成器表達式,即將列表推導式的[]換成()即是
字典推導式:
# 字典推導式 my_dict = {'MJ1':22, 'MJ2':23, 'MJ3':24} reversed_dict = {value:key for key, value in my_dict.items()} print(reversed_dict)
集合推導式:
# 集合推導式 my_set = {key for key, value in my_dict.itmes()} # 只是把key放集合里 print(type(my_set)) print(my_set)
三、深入set、dict
dict常用方法:
a = {'name1':{'age':22}, 'name2':{'age':23}} a.clear() # 清空 new_dict = a.copy() # 淺拷貝 new_dict['name1']['age'] = '25' # a 會隨之被修改 import copy new_dict = copy.deepcopy(a) # 深拷貝 new_dict['name1']['age'] = '26' # a 不變 #formkeys new_list = ['name1','name2'] new_dict = dict.fromkeys(new_list, {'age':23}) # 將可迭代對象轉換成字典dict new_dict.get('name', {}) # 如果key沒有為name,則不會報錯,返回{} for key, value in new_dict.items(): print(key, value) default_value = new_dict.setdefault('name','27') # 不但將值取出,還將 'name':'27' 映射到字典里 new_dict.update(name='29', name3='30') #new_dict.update([('name','31')]) #new_dict.update((('name','31'),))
set 和 frozenset:
#set 集合 fronzenset (不可變集合) 無序, 不重復 # s = set('abcdee') # s = set(['a','b','c','d','e']) s = {'a','b', 'c'} # s = frozenset("abcde") #frozenset 可以作為dict的key # print(s) #向set添加數據 another_set = set("cef") re_set = s.difference(another_set) re_set = s - another_set re_set = s & another_set re_set = s | another_set #set性能很高 # | & - #集合運算 print(re_set) print (s.issubset(re_set)) # if "c" in re_set: # print ("i am in set")
- dict查找的性能遠遠大於list
- 在list中隨着list數據的增大 查找時間會增大
- 在dict中查找元素不會隨着dict的增大而增大
- dict的key或者set的值 都必須是可以hash的
- 不可變對象 都是可hash的, 如str, fronzenset, tuple,自己實現的類 __hash__
- dict的內存花銷大,但是查詢速度快, 自定義的對象 或者python內部的對象都是用dict包裝的
- dict的存儲順序和元素添加順序有關
- 添加數據有可能改變已有數據的順序
四、對象引用、可變性和垃圾回收
1、== 與 is 的區別
==:判斷對象內容是否相等
is :主要判斷id是否相等
注意:當兩個變量值為較小的int或者str等值,而不是list、set等,它們的id是同一個,這是python內部的優化機制,如:
a = 1 b = 1 print(a == b) # True print(a is b) # True ,ip地址相同 a = [1,2,3,4] b = [1,2,3,4] print(a == b) # True print(a is b) # False ,ip地址不同
2、del語句和垃圾回收機制
- python中垃圾回收的算法是采用 引用計數,引用計數減到0時刪除對象
- 刪除變量並不表示刪除對象(釋放內存空間),只有對象或類引用計數器減為0才是刪除對象,釋放內存空間
a = object() # object的引用計數為1 b = a # object的引用計數變為2 del a # 刪除a,object的引用計數減成1,當為0時則被釋放掉 print(b) print(a) # 報錯,找不到 class A: def __del__(self): pass
五、元類編程
1、property
from datetime import date, datetime class User: def __init__(self, name, birthday): self.name = name self.birthday = birthday self._age = 0 @property def age(self): # get方法 return datetime.now().year - self.birthday.year @age.setter # set方法 def age(self, value): self._age = value
2、__getattr__、__getattribute__魔法函數
- 當調用某屬性時,如果找不到,則會調用 __getattr__方法
- 只要是__init__定義的屬性,當被調用時,會優先調用 __getattribute__方法,優先級大於__getattr__方法
from datetime import date class User: def __init__(self,info={}): self.info = info def __getattr__(self, item): #找不到相應屬性時調用 return self.info[item] def __getattribute__(self, item): return "db" if __name__ == "__main__": user = User(info={"company_name":"imooc", "name":"sb"}) print(user.test) # 返回:sb print(company_name) # 返回:sb # init中定義的屬性被調用時會優先調用__getattribute__方法,故打印的都是該方法中的返回值
3、屬性描述符和屬性查找過程
- 數據屬性描述符:實現 get、set、delete 方法
- 非數據屬性描述符:只實現 get方法
實例:
from datetime import date, datetime import numbers class IntField: # 數據屬性描述符 def __get__(self, instance, owner): return self.value def __set__(self, instance, value): if not isinstance(value, numbers.Integral): raise ValueError("int value need") if value < 0: raise ValueError("positive value need") self.value = value def __delete__(self, instance): pass class NonDataIntField: # 非數據屬性描述符,只實現 get def __get__(self, instance, owner): return self.value class User: # 屬性描述符對象 age = IntField() if __name__ == "__main__": user = User() user.age = 30 print(user.__dict__) # 返回:{} print(getattr(user, 'age')) user.__dict__['age'] = 'abc' print(user.__dict__) # 返回:{'age':'abc'} print(user.__dict__['age']) # 返回:abc
屬性查找過程:
如果user是某個類的實例,那么user.age(或getattr(user,’age’)方法): 首先會調用__getattribute__,在__getattribute__內部會調用描述符__get__方法,如果沒有找到方法拋出異常(AttributeError ),此時會調用__getattr__方法,如都沒有則拋AttributeError異常。 #方法調用順序:__getattribute方法__ → __getattribute__內部調用__get__方法 → __getattr__方法 屬性查找過程: user = User(), 那么user.age 順序如下(參考上述實例): 1、如果“age”是在User類中定義或在其基類的__dict__中, 且age是data descriptor(數據屬性描述符), 那么調用該類的__get__方法, 2、如果“age”出現在user實例對象的__dict__中, 那么直接返回 user.__dict__[‘age’], #user.__dict__['age']=10 ,此時數據是存於user對象的__dict__中的, 3、如果“age”出現在User或其基類的__dict__中: 3.1、如果age是non-data descriptor,那么調用其__get__方法 3.2、返回 __dict__[‘age’] 4、如果User有__getattr__方法,調用__getattr__方法,否則 5、拋出AttributeError # 順序由上往下:先查類屬性 → 數據屬性描述符 → 實例屬性 → 非數據描述符 → AttributeError異常
4、__new__和__init__的區別
__new__ 是用來控制類對象的生成過程,在對象生成之前調用。
__init__ 是在 __new__ 生成類對象之后調用。
注意:如果new方法不返回對象,則不會調用init函數
class User: def __new__(cls, *args, **kwargs): print('in new') return super().__new__(cls) def __init__(self, name): self.name = name if __name__ == '__main__': user = User(name='sb')
5、自定義元類
類也是對象,typ是創建類的類,type叫元類。
type → 創建類class對象 → 創建對象
創建類的三種方法:
5.1、常用方法:
def create_class(name): if name == 'user': class User: def __str__(self): return 'user' return User elif name == 'company': class Company: def __str__(self): return 'company' return Company
5.2、type 動態創建:
# type(obj_name,bases,dict) def say(self): return 'i am user' class BaseClass: def answer(self): return 'i am baseclass' if __name__ == '__main__': #type動態創建類 MyClass = type('User', (BaseClass,), {'name':'user', 'say':say}) my_obj = User() print(my_obj) # 返回:<__main__.User object at ...> print(my_obj.name) # 返回:user print(my_obj.say) # 返回:i am user print(my_obj.answer()) # 返回:i am baseclass
5.3、metaclass基類控制類創建:
類實例化過程(實例化user):首先會向繼承的類中尋找metaclass,如果有,則會通過metaclass指向的類創建User類,再實例化對象(控制User類的操作可以再metaclass指定的類中完成)→ 如沒有metaclass,則會由type創建類
class MetaClass(type): # 基類繼承type def __new__(cls, *args, **kwargs): return super().__new__(cls, *args, **kwargs) class User(metaclass=MetaClass): def __init__(self, name): self.name = name def __str__(self): return 'user' if __name__ == '__main__': my_obj = User(name='MJ') print(my_obj) # 返回:user
六、迭代器和生成器
1、迭代器和可迭代對象
迭代器是訪問集合內元素的一種方式,一般用來遍歷數據。
迭代器和以下標的訪問方式不一樣,迭代器是不能返回的,迭代器提供了一種惰性訪問數據的方式。
實現魔法函數:
__iter__ :可迭代對象
實現魔法函數:
__iter__、__next__ :迭代器
from collections.abc import Iterator class Company(object): # 可迭代對象 def __init__(self, employee_list): self.employee = employee_list def __iter__(self): return MyIterator(self.employee) #使用下面自定義的MyIterator迭代器 class MyIterator(Iterator): # 迭代器 def __init__(self, emplyee_list): self.iter_list = employee_list self.index = 0 def __next__(self): # 真正返回迭代值的邏輯 try: word = self.iter_list[self.index] except IndexError: raise StopIteration self.index += 1 return word if __name__ == '__main__': company = Company(['tom', 'bob', 'jane']) my_itor = iter(company) #while True: # try: # print(next(my_itor)) # except StopIteration: # pass # 相當於: #for item in company: # print(item)