廖雪峯Python學習筆記三-面向對象(Java基礎)

使用面向對象的由來和哲學原理不用說了,各語言中面向對象的基本的機制和實現差不多,主要是Python和Java中的差異
###1、創建###
比如:

class Studentpass
Student xm
xm.name = 'xiaoming'

Python中的對象屬性不用預先聲明,可以再運行過程中自由的創建並賦值。

###2、私有成員###
成員包括變量和方法,Python中沒有表明私有的關鍵字,使用前面加兩條下滑__name來創建,且必須在類的內部創建。
表示爲私有變量之後,該成員只能在內部可以訪問,名字不變。
該成員不能在外部直接訪問,(Python解釋器會將成員名變成_Student_name,所有仍然可以通過這個在外部進行訪問,但是強烈不建議這樣做,這可能會帶來很大的隱藏的破壞。)

注意一種情況, 如果使用xm.__name = 'xiaohua'會創建一個新的非私有的成員變量,但不會給內部的私有變量__name 賦值(成員方法一樣的)。(一會兒是創建,一會兒是賦值)

(另外的,在Python類方法裏面訪問成員必須加self,不太方便呀)

###3、繼承和多態###
和Java不同,Python採用多繼承。
鴨子類型
多態是對傳入方法的參數等調用某個方法,當傳入不同的對象時給改參數時產生的行爲不一樣。
由於Python是動態語言,和靜態語言不同的是,傳入的對象並不需要是某個類的子類(屬於某一類族),只需要它具有那些需要調用的方法即可。這就是Python的鴨子類型,“一個對象只要看起來像鴨子,走起路來像鴨子,就可以被看做是鴨子”。

###4、獲取類信息###
類信息,類信息就是變量或者對象或者函數具有的類型的信息,比如是哪種類型,有哪些成員等。
4.1 類型信息
使用type(xxx)方法獲取xxx變量的類型信息,它會返回相應的類對象。
判斷相同:

import types
def fn():
...     pass
...
type(fn)==types.FunctionType
type(lambda x: x)==types.LambdaType
type((x for x in range(10)))==types.GeneratorType
type('abc')==type('123')

判斷類型還有sInstance方法,並且支持傳入多種類型進行判斷。
isinstance((1, 2, 3), (list, tuple))
4.2 方法和成員信息
dir(instance)
會用字符串的list的形式列出所有的成員方法和成員變量。
獲取、設置成員變量的值、判斷成員是否存在
getattr()、setattr()以及hasattr()
getattr(obj, ‘z’, 404) # 獲取屬性’z’,如果不存在,返回默認值404
setattr(obj, ‘y’, 19) # 設置一個屬性’y’

###動態綁定變量和方法以及限定:###
由於Python動態語言的特性,可以給實例和類動態的綁定變量和方法。對一個實例綁定的對其它實例不起作用,對類綁定的會對所有的實例起作用。

如果想要類和實例不被動態綁定其它的屬性(貌似方法也不行),就相當於靜態語言的預定義屬性,可以使用 __slots__變量,如:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱,如果綁定其它就不行,這就相當於靜態語言的預定義屬性了

如果此時綁定其它的屬性就不行了。
動態綁定的限制對繼承的子類不起作用。

###多重繼承###
和Java不同,Python允許多重繼承。再多重繼承的條件下設計繼承關係時,一般讓主線單一繼承下來,然後需要混入額外的功能時,通過多重繼承實現,額外繼承的類通常取名爲XxxMixIn。
例如,Python自帶了TCPServer和UDPServer這兩類網絡服務,而要同時服務多個用戶就必須使用多進程或多線程模型,這兩種模型由ForkingMixIn和ThreadingMixIn提供。通過組合,我們就可以創造出合適的服務來。
編寫一個多進程模式的TCP服務,定義如下:

class MyTCPServer(TCPServer, ForkingMixIn):
    pass

編寫一個多線程模式的UDP服務,定義如下:

class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

###類的操作符重載###
操作符重載在一些圈子中名聲不好,他可能被濫用,導致缺陷或者意料之外的性能瓶頸。但是,如果使用得當,它能很大程度的的提升代碼簡潔性和可讀性。Python對它做了一定的限制,做好了靈活性,可用性和安全性的平衡。Python中運算符重載的基本規則:
(1) 不能重載內置類型的運算符;
(2) 不能自定義運算符,只能重載現有的。
(3)某些運算符不能重載,is and or 以及not,位運算符& | ~是可以重載的。
Python運算符重載列表
Method Overloads Call for
init 構造函數 X=Class()
del 析構函數 對象銷燬
repr 打印轉換 print X,repr(X)
str 打印轉換 print X,str(X)
call 調用函數 X()
_getattr 限制 X.undefine
setattr 取值 X.any=value
getitem 索引 X[key],For If
setitem 索引 X[key]=value
len 長度 len(X)
iter 迭代 In
add + X+Y,X+=Y
sub - X-Y,X-=Y
mul * X*Y
radd 右加+ +X
iadd += X+=Y
or | X|Y,X|=Y
cmp 比較 == X==Y,X<Y
lt 小於< X<Y
eq 等於= X=Y

1、重載根據下標獲取Item
這個是比較複雜一點的,因爲按下表獲取值,傳入的參數有多種,需要分別處理:

class Fib(object):

    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):
            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[a:b:c] 指定間隔的,有負數的,set或者dict用key獲取值的

2、可以重載.運算符
即獲取對象的屬性或者方法時,如果找不到相應的屬性或者方法,就會觸發這個函數,返回相應的結果。
一種應用,拼接網址URL時,使用:
利用完全動態的__getattr__,我們可以寫出一個鏈式調用:

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

    __repr__ = __str__

試試:

>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

以上代碼,在__getattr__時返回的是拼接了參數的Chain對象,繼續調用未知的屬性時,繼續拼接,以此一直循環往復,將整個URL拼出來。不過特殊字符如何拼出來?
更神奇的

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

    __call__ = __getattr__

    __repr__ = __str__

用print(Chain().users(‘Zhangsan’).repos)調用
輸出:/users/Zhangsan/repos

本來Chain() 不能當函數用,通過__call__ = __getattr__把它變成可以當函數調用的,然後在__getattr__中返回它就可以傳遞參數了,即chain().users(‘Zhanshan’)這個寫法有效了,user和參數都傳進去了。這樣的每個調用都可以傳參print(Chain().users(‘Zhangsan’).repos.transferParmas(“transfer a parameter”))
這裏可以看到函數式編程對函數使用的靈活性,將函數對象各種賦值傳遞,以及各種參數,各種使用方式。

3、特別一點的,還可以重載把對象當做一個方法進行調用, XXX(xxx…)
通過重寫__call__方法實現,實際上在Python裏面方法和對象沒有本質區別。
比如

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

    def __call__(self):
        print('My name is %s.' % self.name)

調用方式如下:

>>> s = Student('Michael')
>>> s() # self參數不要傳入
My name is Michael.

判斷對象是否可以當做方法調用,使用callable()方法即可,比如
callable(Student())
True
callable(str)
False

使用枚舉類

Python 3支持枚舉, 枚舉也就是若干個常量放到一個類中,這些常量有兩個屬性,name和int類型的value,可以獲取name, value,判斷相等,迭代功能。
目前知道的兩種創建枚舉的方法,簡潔版的創建

from enum import Enum #需要導入模塊

Month = Enum('Month',('Feb', 'Jen', 'Mar', 'Apr', 'Aay', 'Jun', 'Jul', 'august', 'Sep', 'Oct', 'Nov', 'Dec')

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

name指名稱,member值Month.xxx全名;

完整版的創建,可以更詳細的控制

from enum import Enume, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被設定爲0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6


print(Weekday.Fri)
print(Weekday(1))
print(Weekday(2))
print(Weekday(1) == Weekday.Mon)
print(Weekday(1) == Weekday.Sun)
print(Weekday.Sat.name + ':' + str(Weekday.Sat.value))

可以看到,定義方法是繼承Enum類,然後可以在裏面設置枚舉變量及其值。

使用時可以判斷相等,可以迭代,可以直接訪問,可以通過值訪問,可以獲取名稱及值。

使用元類

Python是一種動態語言,它的類和函數等的編譯解析是在運行期間做的。
Java是靜態語言,不過他也可以進行動態加載,通過ClassLoader。那麼Python解析加載的是通過什麼實現的呢。就是type函數,Python執行過程中,當載如某個模塊時,就會依次該模塊的所有語句。(注意是執行一次,如果有print語句,會輸出的。導入模塊時只有發生了加載纔會執行,而加載是看內存中有沒有,以及是否是最新修改的文件,沒有或不是最新的就會重新加載執行一次,否則不執行。可以主動使用Reload方法。

用type創建
具體的 原來知道的type的用法type(類Class或者實例Instance)獲取到類或實例所屬的類型,它的用法不止如此。它主要是用來加載類的,比如有一個Hello類,通常創建類的方法是:

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

這是正常情況下直接在代碼中定義創建,還可以直接用type創建

def fn(self, name='world'): # 先定義函數
     print('Hello, %s.' % name)
Hello  = type('Hello', (object,), dict(hello = fn))

type()函數依次傳入3個參數:
1.class的名稱;
2.繼承的父類集合,注意Python支持多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
3.class的方法名稱與函數綁定,這裏我們把函數fn綁定到方法名hello上。

這就創建好了一個Hello類,和前面正常情況下創建是一樣的。

控制類的創建行爲
除了使用前面的type創建外,還可以用mateclass控制類的創建行爲。mateclass直譯即爲元類。metaclass允許你創建類或者修改類。換句話說,你可以把類看成是metaclass創建出來的“實例”。
metaclass是Python面向對象裏最難理解,也是最難使用的魔術代碼。正常情況下,你基本上不會碰到需要使用metaclass的情況。

使用@property
設置實例屬性的時候,常用setter,進行參數檢查等,每次調用setter方法會比較麻煩,所以使用
@birth.setter
def birth(self, value):
self._birth = value
這個修飾符之後,可以直接把該方法等價於一個屬性,然後直接賦值就行了,此時相當於調用了該函數,可以進行參數檢查等操作。
同樣的,可以設置getter函數,
只設置其中一個,那麼可以實現某種程度上的私有

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章