py16 面向對象深刻

類的繼承

什麼是繼承:html

在python中,新建的類能夠繼承一個或多個父類,經過繼承建立的新類稱爲「子類」或「派生類」,被繼承的類稱爲「基類」、「父類」或「超類」。java

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

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

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

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

查看繼承linux

>>> 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__)的實現sql

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

繼承的實現原理

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

>>> 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中子類能夠同時繼承多個父類,若是繼承了多個父類,那麼屬性的查找方式有兩種,分別是:深度優先和廣度優先app

示範代碼函數

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,即雙下滑線開頭的屬性在繼承給子類時,子類是沒法覆蓋的。

這種變形須要注意的問題是:

一、這種機制也並無真正意義上限制咱們從外部直接訪問屬性,知道了類名和屬性名就能夠拼出名字:_類名__屬性,而後就能夠訪問了,如a._A__N

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

三、在繼承中,父類若是不想讓子類覆蓋本身的方法,能夠將方法定義爲私有的

#正常狀況
>>> 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

事例:

class A:
    def __test(self):
        print("A.test")

    def func(self):
        print("A.func")
        self.__test()

class B(A):
    def __test(self):
        print("B.test")

b = B()
b.func()

###############
A.func
A.test
###############


class A:
    def test(self):
        print("A.test")

    def func(self):
        print("A.func")
        self.test()

class B(A):
    def test(self):
        print("B.test")

b = B()
b.func()

##################
A.func
B.test

第一個輸出A.test的緣由是類A中func中self.__test()在解釋時已經變造成self._A__test(),因此調用的是父類中的__test方法,這也符合隱藏方法只能在當前類調用的原則

封裝的意義

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

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)
print(f.__dict__)
# f.name=10 #賦值,調用setter,拋出異常'TypeError: 10 must be str'
# del f.name  # 刪除,調用deleterious,拋出異常'TypeError: Can not delete'

#############
egon
{'_Foo__NAME': 'egon'}

staticmethod

在類的方法定義語句上方寫上@staticmethod表明其爲靜態方法,靜態方法沒有默認的self參數,和普通函數同樣,至關於類的一個工具方法。固然也能夠給他傳self(對象名),可是這就失去了靜態方法的意義。

classmethod

在類的方法定義語句上方寫上@classmethod把類中的函數定義成類方法,默認攜帶cls參數,cls表明調用的類

類方法,實例方法,靜態方法的對比

class Foo(object):
    """類三種方法語法形式"""

    def instance_method(self):
        print("是類{}的實例方法,只能被實例對象調用".format(Foo))

    @staticmethod
    def static_method():
        print("是靜態方法")

    @classmethod
    def class_method(cls):
        print("是類方法")

foo = Foo()
foo.instance_method()
foo.static_method()
foo.class_method()
print('----------------')
Foo.static_method()
Foo.class_method()

  實例方法只能被實例對象調用,靜態方法(由@staticmethod裝飾的方法)、類方法(由@classmethod裝飾的方法),能夠被類或類的實例對象調用。
實例方法,第一個參數必需要默認傳實例對象,通常習慣用self。
靜態方法,參數沒有要求。
類方法,第一個參數必需要默認傳類,通常習慣用cls。

類方法的意義與做用:

1.類方法用在模擬java定義多個構造函數的狀況。 因爲python類中只能有一個初始化方法,不能按照不一樣的狀況初始化類。參考django Model instance reference 請看下面的代碼。

class Book(object):

    def __init__(self, title):
        self.title = title

    @classmethod
    def create(cls, title):
        book = cls(title=title)
        return book

book1 = Book("python")
book2 = Book.create("python and django")
print(book1.title)
print(book2.title)

  特別說明,靜態方法也能夠實現上面功能,當靜態方法每次都要寫上類的名字,不方便

2.使用類方法能夠知道是哪一個類在調用該類方法:

class A:
    @classmethod
    def f(cls):
        print(cls)
class B(A):
    pass
A.f()
B.f()

---------------------
<class '__main__.A'>
<class '__main__.B'>

  調用f的時候,cls參數會根據實際調用的類來傳入,從而你能夠知道究竟是誰在call

靜態方法的意義與做用:

靜態方法有點像函數工具庫的做用,而類成員方法則更接近相似Java面向對象概念中的靜態方法。爲何說是工具函數呢,看下面

class D:
    def __init__(self):
        pass

    def save(self):  # 在pycharm中會提示,該方法應該是靜態方法
        print('save')

  pycharm中會提示,save方法應該是靜態方法,爲何呢?由於在該方法中沒有使用任何跟實例對象和該類有關的東西(變量及方法),就好像一個工具方法,一個類的正常封裝時也不該該把工具函數歸於類,因此,把它設爲靜態,這樣和類關係就不大了。

  pycharm中會提示,save方法應該是靜態方法,爲何呢?由於在該方法中沒有使用任何跟實例對象和該類有關的東西(變量及方法),就好像一個工具方法,一個類的正常封裝時也不該該把工具函數歸於類,因此,把它設爲靜態,這樣和類關係就不大了。

添加了@staticmethod以後,就沒有波浪線了,可是這裏注意,雖然靜態方法和類的關係並不大了,可是類名.__dict__裏任然有該靜態方法

靜態方法和類方法使用實例:

類中靜態方法方法調用靜態方法的狀況。下面的代碼,靜態方法調用另外一個靜態方法,若是改用類方法調用靜態方法,可讓cls代替類,讓代碼看起來精簡一些,並且當類名修改時,不用在類定義中修改原來的類名,若是是靜態方法還要修改Foo.averag()的Foo。

class Foo(object):
    X = 1
    Y = 2

    @staticmethod
    def averag(*mixes):
        return sum(mixes) / len(mixes)

    @staticmethod
    def static_method():
        return Foo.averag(Foo.X, Foo.Y)

    @classmethod
    def class_method(cls):
        return cls.averag(cls.X, cls.Y)

foo = Foo()
print(foo.static_method())
print(foo.class_method())

繼承類中的區別:從下面代碼能夠看出,若是子類繼承父類的方法,子類覆蓋了父類的靜態方法,子類的實例繼承了父類的static_method靜態方法,調用該方法,仍是調用的父類的方法和類屬性。子類的實例繼承了父類的class_method類方法,調用該方法,調用的是子類的方法和子類的類屬性。

class Foo(object):
    X = 1
    Y = 2

    @staticmethod
    def averag(*mixes):
        return sum(mixes) / len(mixes)

    @staticmethod
    def static_method():
        return Foo.averag(Foo.X, Foo.Y)

    @classmethod
    def class_method(cls):
        return cls.averag(cls.X, cls.Y)


class Son(Foo):
    X = 3
    Y = 5

    @staticmethod
    def averag(*mixes):
        return sum(mixes) / 3

p = Son()
print(p.static_method())
print(p.class_method())
# 1.5
# 2.6666666666666665 

  注意,類方法和靜態方法使用時都是類名(或對象).方法名,不能直接a = 方法名()這樣調用。

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

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

1、知識儲備

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

3、建立類的兩種方式

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

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

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

方式一:使用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__

類實例化對象實際上經歷三件事:一、產生空對象obj 二、初始化 三、返回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) {}

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

        #二、必定記得返回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方法後,須要完成三個指標,一、產生空對象obj 二、初始化 三、返回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)
相關文章
相關標籤/搜索