多态與多态性
一,多态
1,多态指的是一類事物有多種形态(python裡面原生多态)
1.1動物有多種形态:人,狗,豬
import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #動物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #動物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #動物的形态之三:豬
def talk(self):
print('say aoao')
1.2 檔案有多種形态:文本檔案,可執行檔案
import abc
class File(metaclass=abc.ABCMeta): #同一類事物:檔案
@abc.abstractmethod
def click(self):
pass
class Text(File): #檔案的形态之一:文本檔案
def click(self):
print('open file')
class ExeFile(File): #檔案的形态之二:可執行檔案
def click(self):
print('execute file')
二,多态性
1,什麼是多态動态綁定(在繼承的背景下使用時,有時也稱為多态性)
1.1 多态性是指在不考慮執行個體類型的情況下使用執行個體
在面向對象方法中一般是這樣表述多态性:向不同的對象發送同一條消息(!!!obj.func():是調用了
obj的方法func,又稱為向obj發送了一條消息func),不同的對象在接收時會産生不同的行為(即方法)。
也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實作。
比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者消息一樣,但是執行的效果不同
1.2 多态性分為靜态多态性和動态多态性
靜态多态性:如任何類型都可以用運算符+進行運算
動态多态性:如下
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是動物,隻要是動物肯定有talk方法
#于是我們可以不用考慮它們三者的具體是什麼類型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更進一步,我們可以定義一個統一的接口來使用
def func(obj):
obj.talk()
2,為什麼要用多态性(多态性的好處)
其實大家從上面多态性的例子可以看出,我們并沒有增加什麼新的知識,也就是說python本身就是支援多态性的,這麼做的好處是什麼呢?
1.增加了程式的靈活性
以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用,如func(animal)
2.增加了程式額可擴充性
通過繼承animal類建立了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用
>>> class Cat(Animal): #屬于動物的另外一種形态:貓
... def talk(self):
... print('say miao')
...
>>> def func(animal): #對于使用者來說,自己的代碼根本無需改動
... animal.talk()
...
>>> cat1=Cat() #執行個體出一隻貓
>>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能
say miao
'''
這樣我們新增了一個形态Cat,由Cat類産生的執行個體cat1,使用者可以在完全不需要修改自己代碼的情況下。
使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1)
'''
三,鴨子類型
逗比時刻:
Python崇尚鴨子類型,即‘如果看起來像、叫聲像而且走起路來像鴨子,那麼它就是鴨子’
python程式員通常根據這種行為來編寫程式。例如,如果想編寫現有對象的自定義版本,可以繼承該對象
也可以建立一個外觀和行為像,但與它無任何關系的全新對象,後者通常用于儲存程式元件的松耦合度。
例1:利用标準庫中定義的各種‘與檔案類似’的對象,盡管這些對象的工作方式像檔案,但他們沒有繼承内置檔案對象的方法
#二者都像鴨子,二者看起來都像檔案,因而就可以當檔案一樣去用
class TxtFile:
def read(self):
pass
def write(self):
pass
class DiskFile:
def read(self):
pass
def write(self):
pass
例2:其實大家一直在享受着多态性帶來的好處,比如Python的序列類型有多種形态:字元串,清單,元組,多态性展現如下
#str,list,tuple都是序列類型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))
#我們可以在不考慮三者類型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
len(s)
len(l)
len(t)
綁定方法與非綁定方法
一:類中定義的函數分為兩大類
1:綁定方法(綁定給誰,誰來調用就自動将它本身當作第一個參數傳入):
綁定方法分為綁定到類的方法和綁定到對象的方法,具體如下:
1. 綁定到類的方法:用classmethod裝飾器裝飾的方法。
為類量身定制
類.boud_method(),自動将類當作第一個參數傳入
(其實對象也可調用,但仍将類當作第一個參數傳入)
2. 綁定到對象的方法:沒有被任何裝飾器裝飾的方法。
為對象量身定制
對象.boud_method(),自動将對象當作第一個參數傳入
(屬于類的函數,類可以調用,但是必須按照函數的規則來,沒有自動傳值那麼一說)
2:非綁定方法:用staticmethod裝飾器裝飾的方法
1. 不與類或對象綁定,類和對象都可以調用,但是沒有自動傳值那麼一說。就是一個普通工具而已
注意:與綁定到對象方法區分開,在類中直接定義的函數,沒有被任何裝飾器
裝飾的,都是綁定到對象的方法,可不是普通函數,對象調用該方法會自動傳值,而
staticmethod裝飾的方法,不管誰來調用,都沒有自動傳值一說
3:對于綁定方法和非綁定方法舉個例子
在類内部定義的函數,分為兩大類:
一:綁定對象:綁定給誰就由誰來調用,誰來調用就會把調用者當作第一個參數自動傳入
綁定到對象的方法:在類内定義的沒有被任何裝飾器修飾的
class Foo():
def __init__(self,name):
self.name = name
def tell(self):
print('名字是%s'%self.name)
f = Foo('james')
print(f.tell)
# <bound method Foo.tell of <__main__.Foo object at 0x0000021B7AB3C9E8>>
綁定到類的方法:在類内定義的被裝飾器classmethod修飾的方法
# def 定義的兩個都是綁定到對象的方法
class Foo():
def __init__(self,name):
self.name = name
@classmethod
def func(cls): #cls = Foo
print(cls)
print(Foo.func)
# <bound method Foo.func of <class '__main__.Foo'>>
二:非綁定方法:沒有自動傳值這一說法,簡單說就是一個普通方法
非綁定方法:不與類或者對象綁定,誰都可以調用
class Foo():
def __init__(self,name):
self.name = name
@classmethod
def func(cls): #cls = Foo
print(cls)
@staticmethod
def func1(x,y):
print(x+y)
print(Foo.func1)
# <function Foo.func1 at 0x0000023D73765840>
二:綁定方法
綁定給對象的方法(略)
綁定給類的方法(classmethod)
classmehtod是給類用的,即綁定到類,類在使用時會将類本身當做參數傳給類方法的第一個參數(即便是對象來調用也會将類當作第一個參數傳入),python為我們内置了函數classmethod來把類中的函數定義成類方法
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向對象程式設計\test1\db'
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.from_conf() #對象也可以調用,但是預設傳的第一個參數仍然是類
三:非綁定方法
在類内部用staticmethod裝飾的函數即非綁定方法,就是普通函數
statimethod不與類或對象綁定,誰都可以調用,沒有自動傳值效果
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id(): #就是一個普通工具
m=hashlib.md5(str(time.time()).encode('utf-8'))
return m.hexdigest()
print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8>
#檢視結果為普通函數
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8>
#檢視結果為普通函數
四:綁定方法與非綁定方法的使用舉例
import settings
import hashlib
import time
class People:
def __init__(self,name,age,sex):
self.id =self.create_id()
self.name = name
self.age = age
self.sex = sex
def tell_info(self): #綁定到對象啊
print("name:%s Age:%s Sex:%s"%(self.name,self.age,self.sex))
@classmethod
def from_conf(cls):
obj = cls(
settings.name,
settings.age,
settings.sex
)
return obj
@staticmethod
def create_id():
m = hashlib.md5(str(time.time()).encode('utf-8'))
return m.hexdigest()
p = People('tom',18,'male')
#綁定到對象,就應該由對象來調用,自動将對象本身當作第一個參數傳入
# p.tell_info() #tell_info(p)
#綁定給類,就應該由類來調用,自動将類本身當作第一個參數傳入
# p1 = People.from_conf() #from_conf(People)
# p1.tell_info()
#非綁定方法,不與類或者對象綁定,誰都可以調用,沒有自動傳值這一說
p1 = People('tom1',18,'male')
p2 = People('tom2',18,'male')
p3 = People('tom3',18,'male')
print(p1.id)
print(p2.id)
print(p3.id)
# 08885a46a83b92f94c0f4de537fce9c3
# 08885a46a83b92f94c0f4de537fce9c3
# 2b2df79b379a5f7f709ead6268eb3361
五:classmethod 與 staticmethod的差別
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@staticmethod
def from_conf():
return MySQL(settings.HOST,settings.PORT)
# @classmethod #哪個類來調用,就将哪個類當做第一個參數傳入
# def from_conf(cls):
# return cls(settings.HOST,settings.PORT)
def __str__(self):
return '就不告訴你'
class Mariadb(MySQL):
def __str__(self):
return '<%s:%s>' %(self.host,self.port)
m=Mariadb.from_conf()
print(m) #我們的意圖是想觸發Mariadb.__str__,但是結果觸發了MySQL.__str__的執行,列印就不告訴你:
mariadb是mysql
5.1,類方法,靜态方法的定義
Python 是雙面向的,既可以面向函數程式設計,也可以面向對象程式設計,所謂面向函數就是單獨一個. py 檔案,裡面沒有類,全是一些函數,調用的時候導入子產品,通過子產品名.函數名()即可調用,完全不需要類,那麼你可能會問,那要類還有什麼毛用? 類就是用來面向對象程式設計啦,類可以有自己的屬性,類可以建立很多執行個體,每個執行個體可以有不同的屬性,這也就儲存了很多私有的資料,總之都有存在的必要.
面向對象程式設計中,類方法和靜态方法是經常用到的術語,邏輯上将:類方法隻能由類名調用,靜态方法可以由類名或者對象名調用。在python 文法中,類有三種方法,分别是執行個體方法,靜态方法,類方法
class Foo(object):
'''類三種方法文法形式'''
#在類中定義普通方法,在定義普通方法的時候,必須添加self
def instance_method(self):
print("是類{}的執行個體方法,隻能被執行個體對象調用".format(Foo))
#在類中定義靜态方法,在定義靜态方法的時候,不需要傳遞任何類的東西
@staticmethod
def static_method():
print("是靜态方法")
#在類中定義類方法,在定義類方法的時候,需要傳遞參數cls cls即為類本身
@classmethod
def class_method(cls):
print("是類方法")
foo = Foo()
foo.instance_method()
foo.class_method()
foo.static_method()
print("---------------")
Foo.static_method()
Foo.class_method()
可以看出:
執行個體方法隻能被執行個體對象調用,靜态方法(由@staticmethod裝飾的方法)、類方法(由@classmethod裝飾的方法),可以被類或類的執行個體對象調用。
執行個體方法,第一個參數必須要預設傳執行個體對象,一般習慣用self。對象方法中有self參數,類方法有cls參數,靜态方法是不需要這些附加參數(在c++中,是沒有類這個概念)
靜态函數(@staticmethod):即靜态方法,靜态方法是一類特殊的方法,有時候你可能需要填寫一個屬于這個類的方法,但是這些代碼完全不會使用到執行個體對象本身。它主要處理這個類的邏輯關聯,如驗證資料;而且對參數沒有要求。
類方法(@classmethod):即類方法,類方法不是綁定到對象上,而是綁定在類上的方法,它更關注于從類中調用方法,而不是從執行個體中調用方法,如構造重載;
成員函數:執行個體的方法,隻能通過執行個體進行調用;第一個參數必須要預設傳類,一般習慣用cls。
5.2,類方法與靜态方法說明
1:self表示為類型為類的object,而cls表示為類也就是class
2:在定義普通方法的時候,需要的是參數self,也就是把類的執行個體作為參數傳遞給方法,如果不寫self的時候,會發現報錯TypeError錯誤,表示傳遞的參數多了,其實也就是調用方法的時候,将執行個體作為參數傳遞了,在使用普通方法的時候,使用的是執行個體來調用方法,不能使用類來調用方法,沒有執行個體,那麼方法将無法調用。
3:在定義靜态方法的時候,和子產品中的方法沒有什麼不同,最大的不同就是在于靜态方法在類的命名空間之間,而且在聲明靜态方法的時候,使用的标記為@staticmethod,表示為靜态方法,在叼你用靜态方法的時候,可以使用類名或者是執行個體名來進行調用,一般使用類名來調用
4:靜态方法主要是用來放一些方法的,方法的邏輯屬于類,但是有何類本身沒有什麼互動,進而形成了靜态方法,主要是讓靜态方法放在此類的名稱空間之内,進而能夠更加有組織性。
5:在定義類方法的時候,傳遞的參數為cls.表示為類,此寫法也可以變,但是一般寫為cls。類的方法調用可以使用類,也可以使用執行個體,一般情況使用的是類。
6:在重載調用父類方法的時候,最好是使用super來進行調用父類的方法。靜态方法主要用來存放邏輯性的代碼,基本在靜态方法中,不會涉及到類的方法和類的參數。類方法是在傳遞參數的時候,傳遞的是類的參數,參數是必須在cls中進行隐身穿
7:python中實作靜态方法和類方法都是依賴python的修飾器來實作的。靜态方法是staticmethod,類方法是classmethod。
5.3,靜态方法,類方法的使用差別
1:類方法用在模拟java定義多個構造函數的情況
由于python類中隻能有一個初始化方法,不能按照不同的情況初始化類,舉例如下:
class book(object):
def __init__(self,title):
self.title = title
@classmethod
def creat(cls,title):
book = cls(title=title)
return book
book1=book("python")
book2 = book.creat("python is my work")
print(book1)
print(book2)
print(book1.title)
print(book2.title)
2:類中靜态方法方法調用靜态方法的情況
下面的代碼,靜态方法調用另一個靜态方法,如果改用類方法調用靜态方法,可以讓cls代替類,(讓代碼看起來精簡一些,也防止類名修改了,不用在類定義中修改原來的類名)
class foo(object):
x =1
u =1
@staticmethod
def average(*mixes):
return sum(mixes)/len(mixes)
@staticmethod
def static_method():
return foo.average(foo.x,foo.u)
@classmethod
def class_method(cls):
return cls.average(cls.x,cls.u)
a = foo()
print(a.static_method())
print(a.class_method())
六:練習
定義MySQL類
1.對象有id、host、port三個屬性
2.定義工具create_id,在執行個體化時為每個對象随機生成id,保證id唯一
3.提供兩種執行個體化方式,方式一:使用者傳入host和port 方式二:從配置檔案中讀取host和port進行執行個體化
4.為對象定制方法,save和get_obj_by_id,save能自動将對象序列化到檔案中,檔案路徑為配置檔案中DB_PATH,檔案名為id号,儲存之前驗證對象是否已經存在,若存在則抛出異常,;get_obj_by_id方法用來從檔案中反序列化出對象
小提示:建立唯一id之UUID
原文連結:http://www.cnblogs.com/dkblog/archive/2011/10/10/2205200.html
Python官方Doc:《20.15. uuid — UUID objects according to RFC 4122》
UUID的算法介紹:《A Universally Unique IDentifier (UUID) URN Namespace》
概述:
UUID是128位的全局唯一辨別符,通常由32位元組的字元串表示。
它可以保證時間和空間的唯一性,也稱為GUID,全稱為:
UUID —— Universally Unique IDentifier Python 中叫 UUID
GUID —— Globally Unique IDentifier C# 中叫 GUID
它通過MAC位址、時間戳、命名空間、随機數、僞随機數來保證生成ID的唯一性。
UUID主要有五個算法,也就是五種方法來實作:
1、uuid1()——基于時間戳
由MAC位址、目前時間戳、随機數生成。可以保證全球範圍内的唯一性,
但MAC的使用同時帶來安全性問題,區域網路中可以使用IP來代替MAC。
2、uuid2()——基于分布式計算環境DCE(Python中沒有這個函數)
算法與uuid1相同,不同的是把時間戳的前4位置換為POSIX的UID。
實際中很少用到該方法。
3、uuid3()——基于名字的MD5散列值
通過計算名字和命名空間的MD5散列值得到,保證了同一命名空間中不同名字的唯一性,
和不同命名空間的唯一性,但同一命名空間的同一名字生成相同的uuid。
4、uuid4()——基于随機數
由僞随機數得到,有一定的重複機率,該機率可以計算出來。
5、uuid5()——基于名字的SHA-1散列值
算法與uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法
使用方面:
首先,Python中沒有基于DCE的,是以uuid2可以忽略;
其次,uuid4存在機率性重複,由無映射性,最好不用;
再次,若在Global的分布式計算環境下,最好用uuid1;
最後,若有名字的唯一性要求,最好用uuid3或uuid5。
編碼方法:
# -*- coding: utf-8 -*-
import uuid
name = "test_name"
namespace = "test_namespace"
print uuid.uuid1() # 帶參的方法參見Python Doc
print uuid.uuid3(namespace, name)
print uuid.uuid4()
print uuid.uuid5(namespace, name)
代碼:
#settings.py内容
'''
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'E:\CMS\aaa\db'
'''
import settings
import uuid
import pickle
import os
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
def save(self):
if not self.is_exists:
raise PermissionError('對象已存在')
file_path=r'%s%s%s' %(settings.DB_PATH,os.sep,self.id)
pickle.dump(self,open(file_path,'wb'))
@property
def is_exists(self):
tag=True
files=os.listdir(settings.DB_PATH)
for file in files:
file_abspath=r'%s%s%s' %(settings.DB_PATH,os.sep,file)
obj=pickle.load(open(file_abspath,'rb'))
if self.host == obj.host and self.port == obj.port:
tag=False
break
return tag
@staticmethod
def get_obj_by_id(id):
file_abspath = r'%s%s%s' % (settings.DB_PATH, os.sep, id)
return pickle.load(open(file_abspath,'rb'))
@staticmethod
def create_id():
return str(uuid.uuid1())
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
# print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.save()
conn1=MySQL('127.0.0.1',3306)
conn1.save() #抛出異常PermissionError: 對象已存在
obj=MySQL.get_obj_by_id('7e6c5ec0-7e9f-11e7-9acc-408d5c2f84ca')
print(obj.host)
其他練習
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now(): #用Date.now()的形式去産生執行個體,該執行個體用的是目前時間
t=time.localtime() #擷取結構化的時間格式
return Date(t.tm_year,t.tm_mon,t.tm_mday) #建立執行個體并且傳回
@staticmethod
def tomorrow():#用Date.tomorrow()的形式去産生執行個體,該執行個體用的是明天的時間
t=time.localtime(time.time()+86400)
return Date(t.tm_year,t.tm_mon,t.tm_mday)
a=Date('1987',11,27) #自己定義時間
b=Date.now() #采用目前時間
c=Date.tomorrow() #采用明天的時間
print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
print(c.year,c.month,c.day)
#分割線==============================
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@staticmethod
def now():
t=time.localtime()
return Date(t.tm_year,t.tm_mon,t.tm_mday)
class EuroDate(Date):
def __str__(self):
return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)
e=EuroDate.now()
print(e) #我們的意圖是想觸發EuroDate.__str__,但是結果為
'''
輸出結果:
<__main__.Date object at 0x1013f9d68>
'''
因為e就是用Date類産生的,是以根本不會觸發EuroDate.__str__,解決方法就是用classmethod
import time
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
# @staticmethod
# def now():
# t=time.localtime()
# return Date(t.tm_year,t.tm_mon,t.tm_mday)
@classmethod #改成類方法
def now(cls):
t=time.localtime()
return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪個類來調用,即用哪個類cls來執行個體化
class EuroDate(Date):
def __str__(self):
return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)
e=EuroDate.now()
print(e) #我們的意圖是想觸發EuroDate.__str__,此時e就是由EuroDate産生的,是以會如我們所願
'''
輸出結果:
year:2017 month:3 day:3
'''
不經一番徹骨寒 怎得梅花撲鼻香