1. 程式人生 > >Android程式猿帶你學python第4章--類

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處理