1. 程式人生 > >py16 面向對象深入

py16 面向對象深入

無需 第三方模塊 mysql sets href 字符 類繼承 老師 查找

類的繼承

什麽是繼承:

在python中,新建的類可以繼承一個或多個父類,通過繼承創建的新類稱為“子類”或“派生類”,被繼承的類稱為“基類”、“父類”或“超類”。

python中類的繼承分為:單繼承和多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass

查看繼承

>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類
(<class ‘__main__.ParentClass1‘>,)
>>> SubClass2.__bases__
(<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)

經典類與新式類

1.只有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類
3.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類

提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現

>>> ParentClass1.__bases__
(<class ‘object‘>,)
>>> ParentClass2.__bases__
(<class ‘object‘>,)

繼承的實現原理

python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如

>>> F.mro() #等同於F.__mro__
[<class ‘__main__.F‘>, <class ‘__main__.D‘>, <class ‘__main__.B‘>, 
<class ‘__main__.E‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>]

為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合並所有父類的MRO列表並遵循如下三條準則:

  1. 子類會先於父類被檢查
  2. 多個父類會根據它們在列表中的順序被檢查
  3. 如果對下一個類存在兩個合法的選擇,選擇第一個父類

在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如果繼承了多個父類,那麽屬性的查找方式有兩種,分別是:深度優先和廣度優先

技術分享圖片

技術分享圖片

示範代碼

class A(object):
    def test(self):
        print(‘from A‘)

class B(A):
    def test(self):
        print(‘from B‘)

class C(A):
    def test(self):
        print(‘from C‘)

class D(B):
    def test(self):
        print(‘from D‘)

class E(C):
    def test(self):
        print(‘from E‘)

class F(D,E):
    # def test(self):
    #     print(‘from F‘)
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類

在子類中調用父類的方法

在子類派生出的新方法中,往往需要重用父類的方法,我們有兩種方式實現

方式一:指名道姓,即父類名.父類方法() 註意:這種調用需要在參數中加self

class Vehicle: #定義交通工具類
     Country=‘China‘
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print(‘開動啦...‘)

class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line

    def run(self):
        print(‘地鐵%s號線歡迎您‘ %self.line)
        Vehicle.run(self)

line13=Subway(‘中國地鐵‘,‘180m/s‘,‘1000人/箱‘,‘電‘,13)
line13.run()

方式二:super() 註意:這種調用不用在參數中加self

class Vehicle: #定義交通工具類
     Country=‘China‘
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print(‘開動啦...‘)

class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相當於實例本身 在python3中super()等同於super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print(‘地鐵%s號線歡迎您‘ %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜單車
    pass

line13=Subway(‘中國地鐵‘,‘180m/s‘,‘1000人/箱‘,‘電‘,13)
line13.run()

這兩種方式的區別是:方式一是跟繼承沒有關系的,而方式二的super()是依賴於繼承的,並且即使沒有直接繼承關系,super仍然會按照mro繼續往後查找

#A沒有繼承B,但是A內super會基於C.mro()繼續往後找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print(‘from B‘)
class C(A,B):
    pass

c=C()
c.test() #打印結果:from B


print(C.mro())
#[<class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘__main__.B‘>, <class ‘object‘>]

組合與繼承

組合就是把一個對象傳給另一個類作為類的參數,或者把對象添加到另一個對象中

>>> class Equip: #武器裝備類
...     def fire(self):
...         print(‘release Fire skill‘)
... 
>>> class Riven: #英雄Riven的類,一個英雄需要有裝備,因而需要組合Equip類
...     camp=‘Noxus‘
...     def __init__(self,nickname):
...         self.nickname=nickname
...         self.equip=Equip() #用Equip類產生一個裝備,賦值給實例的equip屬性
... 
>>> r1=Riven(‘銳雯雯‘)
>>> r1.equip.fire() #可以使用組合的類產生的對象所持有的方法
release Fire skill


class People:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print(‘<%s %s %s>‘ %(self.name,self.period,self.price))

class Teacher(People):
    def __init__(self,name,age,sex,job_title):
        People.__init__(self,name,age,sex)
        self.job_title=job_title
        self.course=[]
        self.students=[]


class Student(People):
    def __init__(self,name,age,sex):
        People.__init__(self,name,age,sex)
        self.course=[]


egon=Teacher(‘egon‘,18,‘male‘,‘沙河霸道金牌講師‘)
s1=Student(‘牛榴彈‘,18,‘female‘)

python=Course(‘python‘,‘3mons‘,3000.0)
linux=Course(‘python‘,‘3mons‘,3000.0)

#為老師egon和學生s1添加課程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)

#為老師egon添加學生s1
egon.students.append(s1)


#使用
for obj in egon.course:
    obj.tell_info() 

抽象類和接口

首先看java為什麽有接口,因為java的類不能多繼承,接口用來實現多繼承,且java接口中方法都為抽象方法。

java中抽象類和接口的區別:抽象類由abstract關鍵字來修飾,接口由interface關鍵字來修飾。抽象類中除了有抽象方法外,也可以有數據成員和非抽象方法;而接口中所有的方法必須都是抽象的,接口中也可以定義數據成員,但必須是常量。

接口

在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿接口的概念

可以借助第三方模塊:http://pypi.python.org/pypi/zope.interface

在python中實現抽象類

#一切皆文件
import abc #利用abc模塊實現抽象類

class All_file(metaclass=abc.ABCMeta):
    all_type=‘file‘
    @abc.abstractmethod #定義抽象方法,無需實現功能
    def read(self):
        ‘子類必須定義讀功能‘
        pass

    @abc.abstractmethod #定義抽象方法,無需實現功能
    def write(self):
        ‘子類必須定義寫功能‘
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法

class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(‘文本數據的讀取方法‘)

    def write(self):
        print(‘文本數據的讀取方法‘)

class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(‘硬盤數據的讀取方法‘)

    def write(self):
        print(‘硬盤數據的讀取方法‘)

class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(‘進程數據的讀取方法‘)

    def write(self):
        print(‘進程數據的讀取方法‘)

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#這樣大家都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

封裝和類中各種特殊方法

封裝

在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)

【非特殊調用情況下,子類和實例化的對象不能繼承,重寫,使用該方法或屬性。特殊調用都可以,原因在下面代碼裏】

#其實這僅僅這是一種變形操作,在解釋器解析class時自動變形
#類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:
#由於只有在解析class定義時才會變形,所以只要是本類外的類或對象都無法訪問原形式

class A:
    __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N
    def __init__(self):
        self.__X=10 #變形為self._A__X
    def __foo(self): #變形為_A__foo
        print(‘from A‘)
    def bar(self):
        self.__foo() #只有在類內部才可以通過__foo的形式訪問到.

#A._A__N是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形

這種自動變形的特點:

  1. 類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
  2. 這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
  3. 在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

這種變形需要註意的問題是:

1、這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N

2、變形的過程只在類的定義是發生一次,在定義後的賦值操作,不會變形

技術分享圖片

3、在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

#正常情況
>>> class A:
...     def fa(self):
...         print(‘from A‘)
...     def test(self):
...         self.fa()
... 
>>> class B(A):
...     def fa(self):
...         print(‘from B‘)
... 
>>> b=B()
>>> b.test()
from B


#把fa定義成私有的,即__fa
>>> class A:
...     def __fa(self): #在定義時就變形為_A__fa
...         print(‘from A‘)
...     def test(self):
...         self.__fa() #只會與自己所在的類為準,即調用_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print(‘from B‘)
... 
>>> b=B()
>>> b.test()
from A

封裝的意義

1:封裝數據,將數據隱藏起來這不是目的。隱藏起來然後對外提供操作該數據的接口才是目的

class Teacher:
    def __init__(self,name,age):
        self.__name=name
        self.__age=age

    def tell_info(self):
        print(‘姓名:%s,年齡:%s‘ %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError(‘姓名必須是字符串類型‘)
        if not isinstance(age,int):
            raise TypeError(‘年齡必須是整型‘)
        self.__name=name
        self.__age=age

t=Teacher(‘egon‘,18)
t.tell_info()

t.set_info(‘egon‘,19)
t.tell_info()

2:封裝方法:目的是隔離復雜度

#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這麽做
#隔離了復雜度,同時也提升了安全性

class ATM:
    def __card(self):
        print(‘插卡‘)
    def __auth(self):
        print(‘用戶認證‘)
    def __input(self):
        print(‘輸入取款金額‘)
    def __print_bill(self):
        print(‘打印賬單‘)
    def __take_money(self):
        print(‘取款‘)

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

各種特殊方法:classmethod,staticmethod,property

staticmethod

在類的方法定義語句上方寫上@staticmethod代表其為靜態方法,該方法與類的聯系不大,靜態方法沒有默認的self參數,和普通函數一樣,當然也可以給他傳self(對象名)

classmethod

把類中的函數定義成類方法,使self.參數無法訪問對象的參數,只能訪問類裏的同名參數

class Role:
    name = "我是類name"

    def __init__(self, name, role, weapon):
        self.name = name  
        self.role = role
        self.weapon = weapon

    @staticmethod
    def stamethod(a,self):
        print(self.name)
        print(a)

    @classmethod
    def clsmethod(cls):  # def clsmethod(self):
        print(cls.name)     # print(self.name)

r1 = Role(‘Chenronghua‘, ‘police‘,  ‘AK47‘)
r1.clsmethod()
r1.stamethod(‘沒有默認參數,self需要自己傳‘, r1)

###############
我是類name
Chenronghua
沒有默認參數,self需要自己傳

property

將一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後計算出來的,這種特性的使用方式遵循了統一訪問的原則

ps:面向對象的封裝有三種方式:
【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什麽大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開

python並沒有在語法上把它們三個內建到自己的class機制中,在C++裏一般會將所有的所有的數據都設置為私有的,然後提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現

class Foo:
    def __init__(self,val):
        self.__NAME=val #將所有的數據屬性都隱藏起來

    @property
    def name(self):
        return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)

    @name.setter    # 當對name賦值時該函數被調用
    def name(self,value):
        if not isinstance(value,str):  #在設定值之前進行類型檢查
            raise TypeError(‘%s must be str‘ %value)
        self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME

    @name.deleter   # 當name被刪除時,該函數被調用
    def name(self):
        raise TypeError(‘Can not delete‘)

f=Foo(‘egon‘)
print(f.name)
# f.name=10 #賦值,調用setter,拋出異常‘TypeError: 10 must be str‘ 
del f.name #刪除,調用deleterious,拋出異常‘TypeError: Can not delete‘ 

元類及元類控制類的創建,實例化過程

參考http://www.cnblogs.com/linhaifeng/articles/8029564.html

一、知識儲備

exec:三個參數

參數一:字符串形式的命令
參數二:全局作用域(字典形式),如果不指定,默認為globals(),把運行中全局變量傳入globals
參數三:局部作用域(字典形式),如果不指定,默認為locals(),把運行中局部變量傳入locals

exec的使用

#可以把exec命令的執行當成是一個函數的執行,會將執行期間產生的名字存放於局部名稱空間中
g={
‘x‘:1,
‘y‘:2
}
l={}

exec(‘‘‘
global x,z
x=100
z=200

m=300
‘‘‘,g,l)

print(g) #{‘x‘: 100, ‘y‘: 2,‘z‘:200,......}
print(l) #{‘m‘: 300}

二 、什麽是元類?

首先要知道python中萬物皆是對象,類也是對象,元類是類的類,是類的模板

元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為

元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例,Foo類是 type 類的一個實例)

type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象

class Foo:
      pass

f1=Foo() #f1是通過Foo類實例化的對象
#type函數可以查看類型,也可以用來查看對象的類,二者是一樣的
print(type(f1)) # 輸出:<class ‘__main__.Foo‘> 表示,obj 對象由Foo類創建
print(type(Foo)) # 輸出:<class ‘type‘>

三、創建類的兩種方式

第一步先看類的創建流程:

首先,要知道,解釋器解釋到新變量時會分配名稱空間,類創建也不例外,類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),然後對名稱空間進行填充。

第二步調用元類來創建類。

方式一:使用class關鍵字

class Chinese(object):
    country=‘China‘
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print(‘%s is talking‘ %self.name)

方式二:就是手動模擬class創建類的過程):將創建類的步驟拆分開,手動去創建

#準備工作:
#創建類主要分為三部分
  1 類名
  2 類的父類(元類)
  3 類體

#類名
class_name=‘Chinese‘
#類的父類
class_bases=(object,)
#類體
class_body="""
country=‘China‘
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print(‘%s is talking‘ %self.name)
"""

步驟一(先處理類體->名稱空間):類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),我們可以事先定義一個空字典,然後用exec去執行類體的代碼(exec產生名稱空間的過程與真正的class過程類似,只是後者會將__開頭的屬性變形),生成類的局部名稱空間,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{‘country‘: ‘China‘, ‘talk‘: <function talk at 0x101a560c8>, ‘__init__‘: <function __init__ at 0x101a56668>}

步驟二:調用元類type(也可以自定義)來產生類Chinense

Foo=type(class_name,class_bases,class_dic) #實例化type得到對象Foo,即我們用class定義的類Foo


print(Foo, Foo.country)
print(type(Foo))
print(isinstance(Foo,type))
‘‘‘
<class ‘__main__.Chinese‘>  China#表示由Chinese類創建,這也是type的第一個參數的作用
<class ‘type‘>
True
‘‘‘

我們看到,type 接收三個參數:

  • 第 1 個參數是字符串‘Chinese’,表示類名,print(Foo)時即可看到,第一個參數和type等號左邊的參數可保持一致
  • 第 2 個參數是元組 (object, ),表示所有的父類
  • 第 3 個參數是字典,這裏是一個空字典,表示沒有定義屬性和方法

補充:若Foo類有繼承,即class Foo(Bar):.... 則等同於type(‘Foo‘,(Bar,),{})

元類控制類的行為(類創建及實例化總流程)

知識儲備:

類默認繼承object,新式類不寫也默認繼承。

__call__方法在對象被調用時被使用,類也是對象,所以會調用父類(元類)的__call__

類實例化對象實際上經歷三件事:1、產生空對象obj 2、初始化 3、返回obj

代碼如下,講解在代碼下方

class Mymeta(type): #繼承默認元類的一堆屬性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError(‘類名首字母必須大寫‘)

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}

        #1、調用self,即People下的函數__new__,在該函數內完成:1、產生空對象obj 2、初始化 3、返回obj
        obj=self.__new__(self,*args,**kwargs)

        #2、一定記得返回obj,因為實例化People(...)取得就是__call__的返回值
        return obj

class People(object,metaclass=Mymeta):
    country=‘China‘

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def talk(self):
        print(‘%s is talking‘ %self.name)

    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj,*args,**kwargs)
        return obj

obj=People(‘egon‘,18)
print(obj.__dict__) #{‘name‘: ‘egon‘, ‘age‘: 18}
#####調試順序,按行號來#####
#1-2-18-19-21-25-2-3-6-33-10-13-29-30-21-22-23-31-16-34 

難以理解的幾個點:

1.為什麽從25跳到2,因為此時People還未被調用,還沒輪到__call__方法出馬,此時參考第二種type創建類的方式,看看__init__的參數是不是和type參數一樣?這裏就是在普通類被實例化之前先調用初始化函數,初始化完成立馬到33進行普通類(普通類也是對象)的調用,然後進入__call__方法。

2.進入call方法之後的13-29及以後:在33行普通類被調用後進入8行的call方法後,需要完成三個指標,1、產生空對象obj 2、初始化 3、返回obj。在元類的__call__方法裏調用了普通類的__new__方法進行三步走,普通類使用object.__new__(cls)來創建空對象,然後對空對象進行初始化(30-21),最後返回對象,大功告成。

元類應用

#應用:定制元類實現單例模式
class Mymeta(type):
    def __init__(self,name,bases,dic): #定義類Mysql時就觸發
        self.__instance=None
        super().__init__(name,bases,dic)

    def __call__(self, *args, **kwargs): #Mysql(...)時觸發

        if not self.__instance:
            self.__instance=object.__new__(self) #產生對象
            self.__init__(self.__instance,*args,**kwargs) #初始化對象
            #上述兩步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)

        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self,host=‘127.0.0.1‘,port=‘3306‘):
        self.host=host
        self.port=port


obj1=Mysql()
obj2=Mysql()

print(obj1 is obj2)

py16 面向對象深入