本系列主要學(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__
賦值一個tuple
,tuple
中的屬性為可以添加的屬性。
#!/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)置的list
、tuple
相似但是還有不同的,比如通過索引查找元素,切片技術(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ù)的意義:
- 當(dāng)前準(zhǔn)備創(chuàng)建的類的對象;
- 類的名字;
- 類繼承的父類集合;
- 類的方法集合。
動態(tài)的修改要創(chuàng)建的類,在特定的情況下會使用到,例如:ORM
即對象-關(guān)系映射 ,也就是metaclass
可以設(shè)計ORM
框架,下一講我們學(xué)習(xí)一下編寫ORM
的框架的原理。