Python3(8) Python 面向?qū)ο蟾呒壘幊?/h1>

本系列主要學(xué)習(xí)Python的基本使用和語法知識,后續(xù)可能會圍繞著AI學(xué)習(xí)展開。
Python3 (1) Python語言的簡介
Python3 (2) Python語法基礎(chǔ)
Python3 (3) Python函數(shù)
Python3 (4) Python高級特性
Python3(5) Python 函數(shù)式編程
Python3(6) Python 模塊
Python3(7) Python 面向?qū)ο缶幊?/a>
Python3(8) Python 面向?qū)ο蟾呒壘幊?/a>
Python面向?qū)ο缶幊逃兴约簭姶蟮囊幻妫纭傍喿宇愋汀保@是動態(tài)語言獨有的特性,上一篇的面向?qū)ο蠡A(chǔ)講了python與其它語言面向?qū)ο蟮墓餐帲@一篇主要講Python中特有的面向?qū)ο筇匦浴V乩^承、定制類、元類等,這一篇應(yīng)該算是Python學(xué)習(xí)當(dāng)中的中級核心篇。

動態(tài)的添加類的屬性方法

面向?qū)ο笾校瑢ο笫穷惖膶嵗惖膶傩院头椒ㄓ谝惑w,由于python是動態(tài)語言,在運行時才創(chuàng)建類,所以python 有動態(tài)添加類屬性與對象屬性的能力。這一下就將靜態(tài)語言比下去了,如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from types import MethodType

class Person(object):
    def __init__(self,name):
        self._name = name
        
    @property
    def name(self):
        return self._name
#動態(tài)添加類的屬性
Person.age = 16

p = Person("張三")
#動態(tài)添加對象的屬性
p.sex = '男'
print(p.name,p.sex,p.age)

p1 = Person('李四')
print(p1.name,p.age)
#對象的屬性只適用于當(dāng)前對象
# print(p1.name,p1.sex,p.age)

def set_name(self,value):
    self._name = value
#動態(tài)添加對象的方法
p.set_name = MethodType(set_name,p)
p.set_name('王五')
print(p.name)

def set_age(self,value):
    self.age = value
#動態(tài)的添加類的方法
Person.set_age = set_age
p.set_age(12)
p1.set_age(13)
print(p.name,p.age)
print(p1.name,p1.age)

輸出結(jié)果:

張三 男 16
李四 16
王五
王五 12
李四 13
  • 動態(tài)添加類的屬性、方法,每個對象都可以用
  • 動態(tài)添加對象的屬性、方法,只有當(dāng)前對象可以用
  • 暴露出動態(tài)語言一個缺點,可以肆意的添加方法,屬性,對于嚴(yán)格安全性要求嚴(yán)格的業(yè)務(wù)明顯不行,所以有一個特殊的標(biāo)識出現(xiàn)了__slots__來限定添加的屬性。

__slots__ 的用法

在類中,用__slots__賦值一個tupletuple中的屬性為可以添加的屬性。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

class Person(object):
    __slots__ = ('name', 'age')  # 用tuple定義允許綁定的屬性名稱


p = Person()
p.name = "張三"
p.age = 16
p.sex = '男'

print(p.name,p.age,p.sex)

輸出結(jié)果:

Traceback (most recent call last):
  File "F:/python/HelloWord/def_func.py", line 12, in <module>
    p.sex = '男'
AttributeError: 'Person' object attribute 'sex' is read-only

上面的錯誤信息就是不允許添加sex 屬性。

  • __solts__ 在子類中,如果使用了就是子類+父類的限制,如果沒有使用父類的限制對子類沒用。
class Person(object):
    __slots__ = ('name', 'age')  # 用tuple定義允許綁定的屬性名稱

class Child(Person):
    # __slots__ = ('weight')
    pass

c = Child()
c.name = "李四"
c.sex = "男"
print(c.name,c.sex)

輸出結(jié)果:

李四 男

如果__slots__放開就會報錯,只支持name、age、weight操作。

@property 的用法

@property的作用就是為變量生成一個裝飾器,用于把一個方法變成屬性調(diào)用的。為了限制屬性的賦值,解決屬性賦值的隨意性。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Person(object):
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise ValueError('name must be an str')
        self._name= value

p = Person()
# p.name = '123'
# p.name = 123
print(p.name)

輸出結(jié)果:

Traceback (most recent call last):
  File "F:/python/HelloWord/def_func.py", line 16, in <module>
    p.name = 123
  File "F:/python/HelloWord/def_func.py", line 11, in name
    raise ValueError('name must be an str')
ValueError: name must be an str

如果將整數(shù)賦值給name 會拋出我們定義的異常,這樣就達到限制name必須str類型的作用。使我們的屬性可控起來。@property用起來立馬感覺代碼優(yōu)美多了,減低了出錯的可能。

多重繼承

python與C++一樣,支持多重繼承,其實與java 中接口與繼承一起用的效果差不多。Python 中稱接口的設(shè)計為 MixIn

MixIn

MixIn 的設(shè)計與接口一樣,通過組合搭配來構(gòu)成一個新的完整的功能,而不是層層繼承來實現(xiàn)。我們來分析一下Python 自帶的TCPServer和UDPServer這兩類網(wǎng)絡(luò)服務(wù)。

#一個多進程模式的TCP服務(wù)
class MyTCPServer(TCPServer, ForkingMixIn):
    pass
#一個多線程模式的UDP服務(wù)
class MyUDPServer(UDPServer, ThreadingMixIn):
    pass
#一個協(xié)程模型
class MyTCPServer(TCPServer, CoroutineMixIn):
    pass

Python中一些功能性的類命名xxxMixIn,所以我們在書寫的時候也采用這種方式,主類采用當(dāng)繼承的方式,一些功能性用多繼承,功能性的類命名采用xxxMixIn的形式。

定制類

我們通過Python中內(nèi)置的一些特殊標(biāo)識去定制一些類

__str__ 和__repr__

__str__用于定義打印的類本身的內(nèi)容,用戶反饋,__repr__也是打印類本身的內(nèi)容,開發(fā)者反饋。也就是通過命令運行時的顯示。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Person(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return 'Person object (name: %s)' % self.name
    __repr__ = __str__

p = Person("張三")
print(p)

輸出結(jié)果:

Person object (name: 張三)

我們演示了__str__的使用,但是__repr__需要命令執(zhí)行,所以可以自己試試。

__iter__

__iter__ 用于將類變成一個迭代對象。一般要與__next__一起使用,接下來我們定義一個可迭代的類

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化兩個計數(shù)器a,b

    def __iter__(self):
        return self # 實例本身就是迭代對象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 計算下一個值
        if self.a > 50: # 退出循環(huán)的條件
            raise StopIteration()
        return self.a # 返回下一個值

f = Fib()
for n in f:
    print(n)

輸出結(jié)果:

1
1
2
3
5
8
13
21
34

輸出的是1~50之間的斐波那契數(shù)列,這樣我們定制了一個可以循環(huán)的類,與Python中內(nèi)置的listtuple相似但是還有不同的,比如通過索引查找元素,切片技術(shù)都是不行的,下面我們下一個標(biāo)識就是改進這個的。

__getitem__

__getitem__ 的作用就是設(shè)置下標(biāo)對應(yīng)的元素的,也可以實現(xiàn)切片 要進行判斷,我們一步到位:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化兩個計數(shù)器a,b

    def __iter__(self):
        return self # 實例本身就是迭代對象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 計算下一個值
        if self.a > 50: # 退出循環(huán)的條件
            raise StopIteration()
        return self.a # 返回下一個值

    def __getitem__(self, n):
        if isinstance(n, int):  # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):  # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L
f = Fib()
print(f[4])
print(f[0:5])

輸出結(jié)果:

5
[1, 1, 2, 3, 5]

這個就使我們定義的Fib有了list的部分功能。這就是鴨子類型的應(yīng)用,我們可以定義我們需要的任何類,包括實現(xiàn)系統(tǒng)內(nèi)置的一些類,簡單一句就是可以 “以假亂真”。

__getattr__

__getattr__的作用是調(diào)用類的方法或?qū)傩詴r,如果沒有的話會走__getattr__的方法。如果沒有__getattr__方法就會報錯。所以__getattr__非常好用,我們可以根據(jù)這個特性設(shè)計好多靈活的東西,比如靈活的拼接調(diào)用鏈接:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

class Chain(object):
    def __init__(self, path = ''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    def users(self, username):
        return Chain(self._path + '/' + username)

print(Chain('post:/').aaa.bbb.user.xxx)

print(Chain('post:/').aaa.bbb.users('zhangsan').xxx)

輸出結(jié)果:

post://aaa/bbb/user/xxx
post://aaa/bbb/zhangsan/xxx

上面實現(xiàn)了一個動態(tài)的生成一個連接,我們采用的鏈試調(diào)用,第一次我們創(chuàng)建的Chain()的實例,_self的值被賦值為post:/,第二次調(diào)用Chain().aaa,aaa不存在,所以進了__getattr__方法,通過拼接_self的值變成post://aaa,......調(diào)用Chain().users()的時候,users()方法被調(diào)用,所以把對應(yīng)的name拼接上,最后一個動態(tài)的鏈接就生成了。
剛開始我也有點悶逼,主要是沒有理解__getattr__的作用,還有就是鏈試調(diào)用也沒看明白導(dǎo)致的。最后通過斷點和打印每一步的數(shù)據(jù)看懂了。終于從牛角尖里出來了。

__call__

__call__的作用是把一個對象變成一個可調(diào)用的函數(shù),其實函數(shù)和對象沒有什么界限,因為在python中一切都是對象。換句話說如果我們類中定義了__call__函數(shù),他的實例就是可以被直接調(diào)用的。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

class Person(object):
    def __init__(self,name):
        self._name = name

    def __call__(self, *args, **kwargs):
        return 'My name is %s.' % self._name

p = Person("yalarc")
print(p())

輸出結(jié)果:

My name is yalarc.

我們直接將對象p當(dāng)做函數(shù)來使用。其實還有一個函數(shù)來判斷一個對象是否可以被直接調(diào)用callable()
···
print(callable(p))
···
輸出結(jié)果:

True

枚舉類

枚舉跟java一樣,也是規(guī)范代碼的寫法。保證代碼更加清晰,規(guī)范

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from enum import Enum, unique

# @unique裝飾器可以幫助我們檢查保證沒有重復(fù)值
# 枚舉的定義
@unique
class Gender(Enum):
    Male = 0
    Female = 1

class Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

# 測試:
bart = Student('Bart', Gender.Male)
if bart.gender == Gender.Male:
    print('測試通過!')
else:
    print('測試失敗!')

輸出結(jié)果:

測試通過!

以上就是枚舉的定義和使用,規(guī)范了int值的具體含義,不會出現(xiàn)歧義。

type() 的高級用法

首先我們再次強調(diào)Python是動態(tài)語言,強大之處不是編譯時生成類,而是在運行時。然后再來看type()之前用他來判斷類的類型,現(xiàn)在我們學(xué)習(xí)他的高級用法,既然他放回的是類型,所以我們可以用它動態(tài)的創(chuàng)建類。這個就非常牛逼了,沒有在java等靜態(tài)語言中聽說過的用法。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)


h = Hello()
h.hello("you")
print(type(Hello))
print(type(h))
print('--------------------------')

def fn(self, name='yalarc'):  # 先定義函數(shù)
    print('My name is, %s.' % name)

Person = type('person', (object,), dict(name=fn))  # 創(chuàng)建Hello class

p = Person()
p.name()
print(type(Person))
print(type(p))

輸出結(jié)果:

Hello, you.
<class 'type'>
<class '__main__.Hello'>
--------------------------
My name is, yalarc.
<class 'type'>
<class '__main__.person'>

看到?jīng)]Person類就是動態(tài)的通過type()創(chuàng)建的。type()的三個參數(shù):

  • class的名稱;
  • 繼承的父類集合,注意Python支持多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
  • class的方法名稱與函數(shù)綁定,這里我們把函數(shù)fn綁定到方法名name上。

元類(metaclass)

metaclass的作用是控制類的創(chuàng)建行為。因為既然能創(chuàng)建類,我們不能讓它沒有規(guī)矩的創(chuàng)建,我們應(yīng)該通過metaclass來做一些修改。我們要給類定規(guī)則,所以要先定義 metaclass,然后創(chuàng)建類。

  • 先定義metaclass,就可以創(chuàng)建類,最后創(chuàng)建實例
  • metaclass允許你創(chuàng)建類或者修改類,相當(dāng)于類是 metaclass創(chuàng)建出來的實例
  • xxxMetaclass我們一般通過 xxx+Metaclass來命名 metaclass

接下來我們給list添加一個 add方法:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# metaclass是類的模板,所以必須從`type`類型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)
# 類
class MyList(list, metaclass=ListMetaclass):
    pass

L = MyList([1,2,3])
print(L)
L.add(0)
print(L)

輸出結(jié)果:

[1, 2, 3]
[1, 2, 3, 0]

MyList就是我們定義的具有add方法的類,我們來看看metaclass的定義,是不是metaclass主要看有沒有__new__標(biāo)識的函數(shù)。__new__參數(shù)的意義:

    1. 當(dāng)前準(zhǔn)備創(chuàng)建的類的對象;
    1. 類的名字;
    1. 類繼承的父類集合;
    1. 類的方法集合。

動態(tài)的修改要創(chuàng)建的類,在特定的情況下會使用到,例如:ORM 即對象-關(guān)系映射 ,也就是metaclass可以設(shè)計ORM框架,下一講我們學(xué)習(xí)一下編寫ORM的框架的原理。

參考

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143186738532805c392f2cc09446caf3236c34e3f980f000

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容