1.鴨子類型和多態(tài)
什么是鴨子類型
當(dāng)一只鳥走起路來像鴨子,游泳起來像鴨子,叫起來像鴨子,就可以稱這只鳥為鴨子.
也就是,當(dāng)我們有很多類都實現(xiàn)了同樣的方法,這些類就可以被看成是同一個種類.
Python中的變量本身是沒有類型的,所以它本身就代表著多態(tài),它可以表示成為任何的類型.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 14:52'
class Cat(object):
def say(self):
print('我是一只貓')
class Dog(object):
def say(self):
print('我是一只狗')
class Duck(object):
def say(self):
print('我是一只鴨子')
animal_list = [Cat(),Dog(),Duck()]
for animal in animal_list:
animal.say()
還有一個例子:list的extend方法
lst.extend(self ,iteralbe)
可以看到iterable就是一種鴨子類型,它代表的是一個可迭代的類型
a = [1,2]
b = [2,1]
c = (3,4)
d = dict(x=1,y=2) # 字典也是可迭代對象,但是默認(rèn)迭代是字典的鍵
e = set()
e.add(5)
e.add(6)
a.extend(b)
a.extend(c)
a.extend(d)
a.extend(e)
print(a)
output:
1, 2, 2, 1, 3, 4, 'x', 'y', 5, 6]
2.抽象基類(abc abstract class的縮寫)
什么是抽象基類
1. 如果有的類繼承了抽象基類,它就必須實現(xiàn)抽象基類中的所有的方法
2. 抽象基類是沒有辦法實例化的,就是通過它不能創(chuàng)建對象
既然Python是基于鴨子類型的,也就是說所有的類增加一些魔法方法就可以實現(xiàn)特殊的特性,它不需要像靜態(tài)語言那樣必須繼承某個類才能獲取一些特性.那為什么需要抽象基類呢?因為有時候,我們可以通過抽象基類來判斷某個是否屬于某種類型.
Python的collections.abc模塊有一些抽象基類,你可以根據(jù)isinstance(object,abcClass)來判定某個類是不是某種類型
如何去模擬一個抽象基類呢?
最簡單的方法是通過拋出異常的方式來實現(xiàn)
# 最簡單的方法就是在基類中拋出一個異常,但是這種方法有一個缺點.
# 就是在調(diào)用方法的時候才會拋出異常
class CacheBase():
def get(self,key):
raise NotImplementedError
def set(self,key,value):
raise NotImplementedError
class RedisCache(CacheBase):
pass
redis_cache = RedisCache()
redis_cache.set('key','value')
這樣有一個缺點,就是在調(diào)用方法的時候才拋出異常,而在初始化的時候沒有問題,有沒有什么辦法可以在初始化的時候就可以拋出異常,告訴你必須實現(xiàn)抽象基類的方法呢.使用abc模塊中的@abc.abstractmethod裝飾器的方式可以實現(xiàn).
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get(self,key):
pass
@abc.abstractmethod
def set(self,key,value):
pass
class RedisCache(CacheBase):
def get(self,key):
pass
def set(self,key,value):
pass
cache = RedisCache()
3.isinstance和type的區(qū)別?
type只會判斷這個對象或者類的直屬對象,而isinstance可以向上追溯到它的父類.所有有時候type沒有isinstance準(zhǔn)確.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 16:53'
class A:
pass
class B(A):
pass
b = B()
print(isinstance(b,B))
print(isinstance(b,A))
print(type(b) is B)
print(type(b) is A)
4. 類變量和實例變量
1. 類變量屬于類,也屬于實例.所有的實例對象和類共有一份類變量.并且類和實例都可以調(diào)用類變量
2. 實例變量屬于實例,不屬于類.實例變量是具體的實例單獨擁有.并且實例變量只能由具體的實例來調(diào)用.不能由類變量來調(diào)用.
3. 如果實例變量和類變量重名,則實例變量調(diào)用的就是自己的實例變量,如果通過實例對象對類變量進(jìn)行賦值,則它不會調(diào)用類變量,而是重新增加一個同名的實例屬性.這點是個坑,有點類似全局變量和局部變量的味道.所以我們在使用的時候,最好的習(xí)慣是使用類來調(diào)用類變量,而減少使用實例對象調(diào)用類變量的情況.其實這里如果用類來調(diào)用,就相當(dāng)于是加了一個global聲明,表示這個變量是類變量.而如果你用實例對象來調(diào)用的時候,如果是訪問,它就會向上查找,如果是修改,例如賦值,它就不會向上查找,如果沒有這個屬性,它就會直接創(chuàng)建
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 16:59'
class A:
aa = 1 # 類變量
def __init__(self,x,y):
self.x = x # 實例變量
self.y = y # 實例變量
a = A(2,3) # a是一個實例
A.aa = 11 # 類變量是所有的實例和類共有的變量,只有一份.
a.aa = 100 # 如果創(chuàng)建的實例變量和類變量重合,則實例變量就多出來了一個屬性aa
# 這樣在調(diào)用的時候獲取的就是實例的屬性,有點類似作用域局部和全局的味道
print(a.x,a.y,a.aa) # 實例找變量的時候,會先找自己的,再找類擁有的
print(A.aa) # 類變量也可以通過類來調(diào)用
# print(A.x,A.y) # 實例變量不能通過類來方法
打印結(jié)果:
2 3 100
11
5.類屬性和實例屬性以及查找順序
屬性: 變量和方法統(tǒng)稱為屬性
Python3采用的屬性查找順序是C3算法,也就是先廣度優(yōu)先,然后有子類會調(diào)用子類的方式.
通過一個類的mro屬性可以查看一個類的屬性查找順序.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:03'
# 新式類
class D:
pass
class E:
pass
class C(E):
pass
class B(D):
pass
class A(B,C):
pass
print(A.__mro__)
(<class
'__main__.A'
>, <class'__main__.B'
>, <class'__main__.D'
>, <class '__main__.C'
>, <class '__main__.E'
>, <class 'object'>)
6.類方法,靜態(tài)方法,實例方法
類方法是屬于類的,所有的對象和類公有這個類方法,用@classmethod裝飾器來裝飾.實例對象和類對象都可以調(diào)用類方法,但是類方法不能使用實例屬性,里面必須有cls參數(shù),也就是一個類參數(shù)
靜態(tài)方法,跟一個普通的函數(shù)定義沒有什么不同,對參數(shù)也沒有要求,它就是普通的函數(shù)放到一個類中,然后加上@staticmethod裝飾器.靜態(tài)方法,一般不能使用實例變量.類對象和實例對象都可以調(diào)用靜態(tài)方法
實例方法,第一個參數(shù)必須是具體的實例對象,我們一般定義的時候使用self來代替,不需要添加額外的裝飾器.實例方法一般只有實例對象才可以調(diào)用,類對象不可以調(diào)用
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:08'
class Date:
# 構(gòu)造初始化函數(shù)
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@staticmethod
def parse_from_string(date_str):
year,month,day = tuple(date_str.split('-'))
return Date(int(year),int(month),int(day))
@classmethod
def from_string(cls,date_str):
year,month,day = tuple(date_str.split('-'))
return cls(int(year),int(month),int(day))
def tomorrow(self):
self.day += 1
return self.day
def __str__(self):
return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day)
if __name__ == '__main__':
new_day = Date(2019,1,2)
print(new_day)
print(new_day.tomorrow())
date_str = '2018-12-31'
# 用staticmethod來完成初始化
new_day = Date.parse_from_string(date_str)
print(new_day)
# 用classmethod來完成初始化
new_day = Date.from_string(date_str)
print(new_day)
7.數(shù)據(jù)封裝和私有屬性.
私有屬性:
通過雙下劃線來聲明私有屬性,不可以直接訪問,但是它只是改變了一個名字不可以直接訪問.但是還是可以通過_classname__var的方式來訪問.
這種方法,其實就是為了使得我們寫程序的時候更加規(guī)范,更多的是一種提示程序員這個變量該怎么使用的作用.
這種方法還有一個好處,就是有效的區(qū)分了繼承之后變量名重名的問題,因為它會在變量前面加上自己的_classname
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:45'
class User:
def __init__(self, birthyear):
self.__birthyear = birthyear
def get_age(self):
# 返回年齡
return 2019 - self.__birthyear
if __name__ == '__main__':
user = User(1990)
# print(user.__birthyear) # 這里訪問不到,以雙下劃線開頭的私有屬性
# 不可以直接通過實例對象來訪問,但是可以通過_User__birthyear來訪問.
print(user.get_age())
8.Python的自省
什么是自省
自省就是在Python中一個對象在運(yùn)行的時候可以通過某種機(jī)制知道自己的類型.
dir()
可以獲取一個對象的多有的屬性列表,只有屬性沒有值.
__dict__
變量可以獲取一個對象的自己的屬性,一般是對象自身擁有的,不是繼承的,也不是內(nèi)置的.
并且它返回的是一個字典,字典的鍵就是屬性名,字典的值就是屬性變量對應(yīng)的值.可以通過這個字典進(jìn)行添加和修改屬性的操作.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 19:57'
class Person:
name = 'user'
class Student(Person):
def __init__(self, school_name):
self.school_name = school_name
if __name__ == '__main__':
user = Student('慕課網(wǎng)')
person = Person()
# 通過__dict__查詢這個類都有哪些屬性
print(user.__dict__) #{'school_name': '慕課網(wǎng)'}
print(person.__dict__) # {}
print(Person.__dict__)
# {'__module__': '__main__', 'name': 'user', '__dict__': <attribute '__dict__' of 'Person' objects>,
# '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
# 可以通過__dict__添加和修改一個對象的屬性
user.__dict__['school_addr'] = '北京市'
print(user.school_addr) # 北京市
print(dir(user))
# dir()函數(shù)會列出一個對象的所有的屬性,包括它繼承到的,還有內(nèi)置的各種屬性
Python中的super()函數(shù)的使用
兩個問題?
1.比如我們已經(jīng)重寫了某個方法,為什么還要用super()調(diào)用它的上一級的這個方法
因為有時候,我們需要重用它的上一級完成的某些功能.比如我們重寫Thread類的時候,就可以重用它的__init__里面的一些功能
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/14 20:12'
# 既然我們重寫了構(gòu)造方法,為什么還要去調(diào)用super()呢?
# 因為我們有時候要重用它的上一級的類的方法去實現(xiàn)某種功能.
# 還有super()并不是簡單的調(diào)用父類的方法,它的調(diào)用順序又是怎么樣的呢?
# super()的調(diào)用順序是按照__mro__的順序來調(diào)用的,也就是說它是和通過.
# 來查詢屬性的順序是一致的.
from threading import Thread
class MyThread(Thread):
def __init__(self, name, target, time):
self.time = time
super().__init__(target=target, name=name)
def run(self):
pass
class A:
def __init__(self):
print('A')
class B(A):
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().__init__()
if __name__ == '__main__':
d = D()
print(D.__mro__)
[output:] D B C A
(<class 'main.D'>, <class 'main.B'>, <class 'main.C'>, <class 'main.A'>, <class 'object'>)
如果只是簡單的調(diào)用父類super()方法,則上面的打印結(jié)果應(yīng)該是 d=D()的時候應(yīng)該是:D,B,A,C,A
但是實際的調(diào)用情況是D B C A和D.__mro__
的順序是一致的
Python中的with語句
首先看下try ... except .. else ... finally的用法
try
: 先執(zhí)行的模塊,這里會捕獲異常.
except
:如果捕獲到異常就會執(zhí)行except語句的代碼
else
: 如果沒有捕獲到異常,就會執(zhí)行else語句的代碼
finally
: 無論except和else是否執(zhí)行了,finally一定會執(zhí)行.
def exe_try():
try:
print('code started')
raise KeyError
return 1
except KeyError as e:
print('key error')
return 2
else: # 沒有拋異常的時候會運(yùn)行
print('other error')
return 3
finally: # 無論是否拋異常都會執(zhí)行,如果finally里面有return語句.則
# 返回的永遠(yuǎn)都是finally里面的return語句
print('finally')
return 4
總結(jié):
finally一般用來釋放資源,做最后的處理.
Python的with語句
with語句又叫上下文管理器,之所以一個對象可以使用with語句,是因為這個對象實現(xiàn)了上下文管理器協(xié)議.
而這個協(xié)議的實現(xiàn),就是通過魔法方法__enter__()
和__exit__
來實現(xiàn)的.
只要我們使用了with語句,它后面跟的對象,在初始化的時候就會調(diào)用這個對象的__enter__()
方法,而在結(jié)束的時候就會自動調(diào)用它的__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表示的含義是初始化的時候先調(diào)用對象的__enter__方法
# 結(jié)束的時候會調(diào)用__exit__方法 __enter__ 和__exit__方法就是
# 用來實現(xiàn)上下文管理器協(xié)議的,然后實現(xiàn)了這個協(xié)議的就可以使用with語句.
with Sample() as simple:
simple.do_something()
還有另外一種方式可以實現(xiàn)上下文管理器,使用contexlib的contextmanager裝飾器可以將一個函數(shù)變成上下文管理器對象.
注意:
這里的函數(shù)必須是使用yield,yield之前的代碼可以理解成都是__enter__()
實現(xiàn)的邏輯代碼.
而yield之后的代碼可以理解成都是__exit__()
實現(xiàn)的代碼邏輯.
@contextlib.contextmanager
def file_open(file_name):
print('file open')
yield {}
print('file end')
with file_open('bob.txt') as f_opened:
print('中間')
output:
file open
中間
file end
這種書寫的好處是,代碼比較容易理解,更加的直觀.