Android程式猿帶你學python第4章--類
導讀
類對於Java同學來說無比熟悉,每個class都是一個類
類包括2個部分:屬性和方法
屬性是用來描述相同物件的靜態特徵
方法是用來描述相同物件的動態特徵
Python中的類
在python中定義一個class
class Person:
//建構函式
def __init__(self):
pass
def getClassDesc(self):
return "Person Class"
self
類方法第一個引數是self,不能省略。用於接收例項化過程中傳入的所有引數,但是在例項化的過程中,這個self不需要外部傳入。這個對於寫Java的同學來說可能比較彆扭。
在python中,外部傳入的資料都可以賦給self,而不需要像Java一樣建立很多成員變數,而self本身就是一個例項物件。python作為一門動態的指令碼語言和Java有一個本質的區別就是,當Java一個類編寫完之後通過編譯得到class檔案就再也不能修改了,而python隨時可以修改類或者例項物件,比如增加一個變數或者刪除一個變數。所以我們可以為self不斷增加變數
def__init__
等價於Java的建構函式
生成例項物件
p = Person()
print(type(p))
print(p.getClassDesc())
類屬性和例項屬性
直接通過類呼叫的變數稱為類屬性,靜態資料
通過將類例項化後,用例項得到的屬性叫做例項屬性
通過實際的例子可以更好的理解
class A:
v = 8
print(A.v)
a = A()
print(a.v)
a.v就是例項屬性 A.v就是類屬性
可以得到結果
8
8
然後我們對a.v進行修改
a = A()
a.v = 9
print(a.v)
print(A.v)
可以得到結果
9
8
可以得出一個結果例項屬性發生變化時不會修改類屬性
這是為什麼呢?
原因就是a.v = 9這個操作其實是例項a生成了一個新的屬性v然後覆蓋了之前的v
不信可以用del a.v試試
a = A()
a.v = 9
print(a.v)
del a.v
print(a.v)
可以得到結果
9
8
反過來如果我們修改類屬性呢
a = A()
A.v = 10
print(a.v)
print(A.v)
可以得到結果
10
10
可以初步得出一個結論:如果類中變數是一個不可變物件時(之前在1章中講過什麼是可變物件),修改例項屬性不能影響類屬性,但是修改類屬性會影響例項屬性
那緊接著我們看看當類中變數是可變物件時,會有什麼結果
class A:
list = [1,2,3,4]
a = A()
A.list.appen(5)
print(A.list )
print(a.list)
a .list.remove(4)
可以得到結果
[1,2,3,4,5]
[1,2,3,4,5]
[1,2,4,5]
[1,2,4,5]
可以得到一個結論,如果類中變數是可變物件,類屬性和例項屬性可以相互影響
繼承
正對於java同學來說肯定不陌生
class Person(object):
pass
多重繼承
區別於Java中的單繼承,python支援多繼承
class Boy(A,B):
pass
繼承的類和屬性廣度優先
怎麼理解呢
來看個例子
#! /usr/bin/env python
#coding=utf-8
class A:
def func(self):
print("A func")
class B:
def func(self):
print("B func")
def getClassName(self):
print("class name : B")
class C(A,B):
def getClassName(self):
print("class name : C")
class D(C):
pass
if __name__ == "__main__":
d = D()
d.func()
d.getClassName()
結果
A func
class name : C
可以看到遍歷路徑是 D->C->A->B
符合之前說的廣度優先
Super
這裡和Java裡的super在表象上看一致,其實沒有半毛錢關係
python中的super工作原理是
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
其中,cls 代表類,inst 代表例項,上面的程式碼做了兩件事:
● 獲取 inst 的 MRO 列表
● 查詢 cls 在當前 MRO 列表中的 index, 並返回它的下一個類,即 mro[index + 1]
當你使用 super(cls, inst) 時,Python 會在 inst 的 MRO 列表上搜索 cls 的下一個類。
舉個例子
class Base(object):
def __init__(self):
print "enter Base"
print "leave Base"
class A(Base):
def __init__(self):
print "enter A"
super(A, self).__init__()
print "leave A"
class B(Base):
def __init__(self):
print "enter B"
super(B, self).__init__()
print "leave B"
class C(A, B):
def __init__(self):
print "enter C"
super(C, self).__init__()
print "leave C"
>>> c = C()
enter C
enter A
enter B
enter Base
leave Base
leave B
leave A
leave C
看下C的方法解析順序
>>> C.mro() # or C.__mro__ or C().__class__.mro()
[__main__.C, __main__.A, __main__.B, __main__.Base, object]
當呼叫super(C, self).init()會執行A中的init(self)方法然後呼叫A中的super(A, self).init()又會根據表中順序找到B依次下去
和Java中直接呼叫父類方法完全不一樣
靜態方法
@staticmethod
類方法
@classmethod
例項方法只能被例項物件呼叫,靜態方法(由@staticmethod裝飾的方法)、類方法(由@classmethod裝飾的方法),可以被類或類的例項物件呼叫。
例項方法,第一個引數必須要預設傳例項物件,一般習慣用self。
靜態方法,引數沒有要求。
類方法,第一個引數必須要預設傳類,一般習慣用cls。
看個例子
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
如果子類繼承父類的方法,子類覆蓋了父類的靜態方法,
子類的例項繼承了父類的static_method靜態方法,呼叫該方法,還是呼叫的父類的方法和類屬性。
子類的例項繼承了父類的class_method類方法,呼叫該方法,呼叫的是子類的方法和子類的類屬性。
私有化
準備私有化的屬性前面加上__
@property
可以呼叫私有化屬性
特殊屬性
dict
slots 申明類屬性,只要初始化了類屬性,例項就不能修改這個屬性了
setattr(self, name, value) 如果給name賦值,就呼叫這個方法
getattr(self, name) 如果name被訪問,同時它不存在,就呼叫這個方法
getattribute(self, name) 當name被訪問,無論存不存在都呼叫
delattr(self, name)如果要刪除name,該方法被呼叫
name
python中if name == ‘main’
在cmd 中直接執行.py檔案,則name的值是’main‘;
而在import 一個.py檔案後,name的值就不是’main‘了;
從而用if name == ‘main‘來判斷是否是在直接執行該.py檔案
獲得例項屬性
先從dict中找,沒有就從類屬性中找
生成器
在需要生成大量資料時,一次性全部讀到記憶體中一定不是一個好辦法,我們用到多少資料就讀取多少資料,可以減輕記憶體開銷,提升程式效能,這就需要一邊迴圈一邊計算。在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator。
要建立一個generator,有很多種方法。第一種方法很簡單,還記得之前講到的列表解析器嗎?只要把一個列表生成式的[]改成(),就建立了一個generator:
>>> l = [x for x in range(10)]
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
生成器內部使用的是迭代器,每用next(g)取一個數據,遊標下移一位,直到取完丟擲StopIteration錯誤.如果用for迴圈取則沒有這個顧慮
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
2
3
4
5
6
7
8
9
和列表不同的是,再迴圈取一次生成器,就得到空,因為遊標已經在最後位置了
>>> for n in g:
... print(n)
...
>>
yield
yield內部實現支援了迭代器協議,同時yield內部是一個狀態機,維護著掛起和繼續的狀態
這是什麼意思呢?我們先來看個例子
def foo():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
f = foo()
for i in f:
print(i)
得到的結果就是
step 1
1
step 2
2
step 3
3
在foo這個函式中加入yield這個關鍵字,這樣foo()函式就變成了一個生成器,它會返回一個生成器型別的物件
再來看看剛剛的概念,維護著掛起和繼續的狀態程式每執行到yiled這裡就會產生一個類似return的效果,中止函式繼續執行,但是和return不同的是,yeild只是把函式掛起了,下次執行next()的時候,就會從當前yiled後面繼續執行
其實,生成器函式返回生成器的迭代器。 “生成器的迭代器”這個術語通常被稱作”生成器”。這也是另一種建立一個generator的方法。
總結
學完這章類,我們就具備了編寫python程式的有絕大數知識,已經可以編寫許多實用的小工具了,在實踐中不斷提升程式設計能力,擴充python知識。推薦大家上github上看看一些大牛的開源框架,看看他們是如何利用這些基礎的語法知識,寫出精妙的框架。在下一章我們可以一起來看下python的I/O處理