天天看點

python 面向對象之多态與綁定方法

多态與多态性

一,多态

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
'''
      

不經一番徹骨寒 怎得梅花撲鼻香