Python學習筆記(五)OOP
模塊
使用模塊import 模塊名
。有的僅僅導入了某個模塊的一個類或者函數,使用from 模塊名 import 函數或類名
實現。為了避免模塊名沖突。Python引入了按文件夾來組織模塊的方法,稱為包(Package)。
則在a文件夾下的b.py模塊名就是a.b
。a文件夾下必須有一個文件__ini__.py
。哪怕是文件內容是空。否則a就成了普通文件夾。
標準模塊文件模板:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
‘a test module‘ #不論什麽模塊的第一個字符串都被視為模塊的文檔凝視
__author__ = ‘Your Name‘
...
def f():
...
if __name__ == ‘__main__‘: #從其它地方導入模塊,if不會通過。因此能夠用來在命令行下進行測試
f()
- 模塊中的非公開函數前綴
_
。如def _private_1():
- 能夠被直接引用的特殊變量一般用
__argname__
這樣的前後各兩個下劃線的形式來表示。自定義的公開函數或變量一般就用普通的形式def f
,不用這樣的下劃線形式。 - 須要註意的是。Python並沒有一種方法能夠全然限制訪問private函數或變量。因此這個得靠自覺了
第三方模塊能夠使用pip安裝pip install numpy
。它會從python的官方站點自己主動下載和安裝。
也能夠自己下載下來之後安裝。
面向對象編程
類語法
class Student(object):
pass
object
是默認父類名稱。實例的創建使用tom = Student()
。作為動態語言,Python支持自由給tom綁定類外的新屬性tom.name = ‘Tom‘
。
假設有須要在創建實例時綁定的屬性。一般寫在類中的__ini__
函數中:
def __ini__(self, arg1, arg2):
self.__arg1 = ...
self.arg2 = ...
__ini__
的第一個參數永遠是self,會自己主動傳入。
有了這樣的__ini__
arg1
和arg2
參數。__arg1
的兩個前下劃線表明這是一個私有屬性,不能被外部訪問。
鴨子類型:僅僅要看起來像鴨子。跑起來像鴨子,那就能夠看做鴨子
Python的動態性也體如今繼承的上。假設有個函數中調用了某類的方法,僅僅要傳入的對象也有這種方法,都能夠運行,而和這個對象是不是該類或該類子類的實例無關。
獲取對象信息
獲取對象的信息能夠使用type(),同一時候假設
import types
。還能夠使用type(對象) == types.type模塊中的常量函數類型
來推斷一個對象是否是某種詳細函數類型。isinstance(對象,類)
的推斷方法則假設該對象是參數中類的父類也返回True。能用type推斷的基本類型都能夠使用isinstance,第二個參數也能夠是個list。僅僅要滿足是元素之中的一個就返回True。dir()能夠將一個對象的全部屬性和方法都返回
當不知道對象細節時,能夠使用
hasattr(obj, ‘arg‘)
得到obj是否有arg屬性或方法;getattr(obj, ‘arg‘)
返回屬性或方法;setattr(obj, ‘arg‘, value)
設置屬性或方法(沒有則直接添加)
面向對象高級編程
使用__slots__
由於Python是動態語言。因此能夠任意為類、實例綁定屬性或者方法。綁定方法要用到from types import MethodType
,s.f = MethodType(f, s)
將方法f綁定到類或實例s上。同一時候要註意f的第一個參數要是self才幹綁定。
假設想要限制實例的屬性,就須要在定義類的時候定義一個特殊變量__slots__ = (‘name1‘,‘name2‘)
來限定給實例綁定的屬性名僅僅能是name1或者name2。
辨析:
from types import MethodType
class Student(object):
__slots__ = (‘name‘, ‘sex‘, ‘set_name‘, ‘set_grade‘)
#同意綁定的在下文中稱為內方法和內屬性。反之稱為外方法和外屬性#
#內方法中綁定內屬性
def set_name(self, name):
self.name = name
#內方法中綁定外屬性#
def set_grade(self, grade):
self.grade = grade
#外方法中綁定外屬性#
def set_score(self, score):
self.score = score
#外方法中綁定內屬性#
def set_sex(self, sex):
self.sex = sex
tom = Student()
jane = Student()
(1)給實例綁定屬性:
tom.name = ‘Tom‘ #正確。name是內屬性
jane.grade = 98 #錯誤,grade是外屬性。不能綁定
print(jane.name) #錯誤,未定義實例jane的name
#第三句可見實例綁定的屬性僅僅算各個實例自己的
(2)給類綁定屬性:
Student.name = ‘name‘ #內屬性能夠綁定
Student.grade = 0 #外屬性也能夠綁定,可見__slots__僅僅作用於實例
tom.name = ‘Tom‘ #錯誤。可見通過這樣的方法綁定的屬性到實例中為僅僅讀
print(jane.name) #輸出為name
(3)給實例綁定方法:
tom.set_name = MethodType(set_name, tom) #內方法,當中綁定內屬性
tom.set_grade = MethodType(set_name, tom) #內方法。當中綁定外屬性。調用方法時會報錯
tom.set_score = MethodType(set_score, tom) #外方法。不可綁定
tom.set_sex = MethodType(set_name, tom) #外方法,不可綁定
(4)給類綁定方法
#綁定內方法
Student.set_name = MethodType(set_name, Student)
tom.name = ‘Tom‘
print(jane.name) #報錯,可見在調用方法綁定內屬性之前。“實例.屬性 =”事實上回到了第一種辨析的情況
tom.set_name(‘Tom‘)
jane.set_name(‘Jane‘) #調用內方法,才將內屬性綁定到了類
tom.name = ‘Tom‘ #報錯。可見上一句綁定的屬性和(3)中同樣。僅僅讀。僅僅能通過類方法改動
print(jane.name)
print(tom.name) #輸出也是Jane,可見通過類方法改動的屬性是類屬性,各實例的相應屬性都是最後一次調用類方法之後的屬性值
#綁定內方法,當中綁定外屬性
Student.set_grade = MethodType(set_grade, Student)
tom.set_grade(4) #調用方法綁定了外屬性
tom.grade = 4 #類屬性僅僅讀。不可直接改動
print(tom.grade) #實例擁有了外屬性,只是是類屬性,僅僅讀,僅僅能通過類方法改動
剩下的兩種情況不再贅述,由於__slots__
不限制類,因此不論什麽方法和屬性都能夠被綁定。僅僅只是通過調用方法綁定的屬性是僅僅讀的,僅僅能通過調用方法改動,不能在實例中直接使用.
賦值改動通過以上辨析,總結例如以下:
__slots__
僅僅限制實例的方法或屬性綁定,不限制類。對類綁定的方法和屬性都能夠在實例中調用,但類屬性在實例中是僅僅讀的,它的值為最後一次調用方法或通過
類.屬性=
賦值的結果,不能在實例中用實例.屬性=
改動。
[email protected]
當須要改動屬性時。為了能夠檢查參數,一般調用方法來設置屬性值。
但這樣的話就略顯麻煩。
[email protected]
@property
def name(self):
return self._name
@name.setter #假設不用下面的部分,則name就變成了僅僅讀屬性
def name(self,value):
...類型檢查等
self._name = value
註意屬性_name
一定要有下劃線來區分屬性和方法,否則名字同樣變成了方法。上面的代碼就成了遞歸。會報現遞歸次數溢出錯誤
多重繼承
假設繼承的多個父類都有同樣的方法,則以第一個為準,後面幾個父類的該方法不會起作用。
定制類
諸如__slots__
這樣的兩端都有兩個下劃線的變量或者函數是有特殊用途的,因此能夠通過定義這類對象來對類進行定制
__str__(self)
:當運行print(類)
時,程序會打印出屬性和內存地址,假設想要打印出特定的內容。則能夠定義一個__str__
方法,輸出特定的內容。
假設不用print而是直接調用類名輸出信息。則須要定義__repr__()
方法。簡單的一種定義方法就是__repr__ = __str__
高速定義。
__iter__(self)
:假設想要叠代一個類則須要定義__iter__
方法返回self
。然後再定義一個__next__(self)
方法運行叠代中要做的事情
__getitem__(self, n)
:把類變成像list那樣能夠按下標取元素操作。
在__getitem__
下返回元素值。假設要實現切片等功能。還要再對這個函數進行進一步的細化,比方推斷傳入的參數是否是slice等一系列興許操作。
同樣地,將類變為像dict那樣,使用__setitem__
。使用__delitem__
刪除元素。
以上的這些方法將類變成list或dict的“鴨子”,使類能夠像list或dict那樣操作。
__getattr__(self, attr)
:返回動態屬性或方法。
這個函數下邊對attr進行推斷。從而綁定規定的屬性或者方法。綁定屬性返回屬性值。綁定方法則返回lambda函數。
假設沒有不論什麽限制,顯然不論什麽屬性或方法都能夠綁定。這時候假設返回自身,則就實現了鏈式調用。利用這一點構造一個針對不同API的SDK。下面的代碼就實現了一個GitHub的API的URL輸出:
class Chain(object):
def __init__(self, path =‘‘):
self._path = path
def __getattr__(self, path):
return Chain(‘%s/%s‘ %(self._path, path))
def __call__(self,name):
return Chain(‘%s/:%s‘ %(self._path, name))
def __str__(self):
return ‘GET ‘ + self._path
print(Chain().users(‘hermanncain‘).repos)
#輸出為GET /users/:hermanncain/repos
__call__(self)
:調用實例自身。
即調用s()
時運行__call__(self)
。也能夠使用__call__(self, *args, **kw)
來傳遞參數,則調用s(*args, *kw)
時運行當中的內容。這樣模糊了類和實例的界限。
枚舉類
from enum import Enum
Month = Enum(‘Month‘, (‘Jan‘, ‘Feb‘, ‘Mar‘, ‘Apr‘, ‘May‘, ‘Jun‘, ‘Jul‘, ‘Aug‘, ‘Sep‘,
‘Oct‘, ‘Nov‘, ‘Dec‘))
for name, member in Month.__member__.items():
print(name, ‘=>‘, member, ‘,‘, member.value)
value為枚舉成員相應的int值,默認從1開始
還有一種從Enum派生的自定義枚舉類。語法就和C++中的枚舉類型有些像了:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0
Mon = 1
...
@unique
裝飾器用於檢查反復值。
對枚舉類的成員有下面幾種引用方法:
print(Weekday.Sun)
print(Weekday[‘Sun‘])
print(Weekday(1))
#以上的輸出都是Weekday.Sun
print(Weekday.Sun.value)
#輸出0
元類*
使用type()
創建類
動態語言的函數和類的定義是在運行時動態創建的。
寫一個a.py
的模塊。在模塊裏定義class A
,然後在主程序中from a import A
,加載模塊時運行模塊全部語句,結果就是動態創建出一個A的class。type(A)
返回值是type
,type(A的實例)
的返回值則是class ‘__main__‘.A
。
可是type()
也能夠用於動態創建類。實際上Python解釋器遇到class
定義時,也是使用type()
動態地將該類創建出來的:
def f(self, ...):
...
A = type(‘A‘, (object,), dict(af = f)
這樣就動態創建了一個A
類。父類是object
(註意多重繼承父類放在tuple
裏。假設僅僅有一個父類則tuple僅僅有一個元素,逗號不可少)。而且有一個名為af
的方法。
metaclass
元類*
類相當於元類的實例。metaclass是Python面向對象裏最難理解,也是最難使用的魔術代碼。一般用不到
*這部分內容先略過,等到用到的時候依據實例來整理
Python學習筆記(五)OOP