1. 程式人生 > >Python學習筆記(五)OOP

Python學習筆記(五)OOP

默認 tro acl 引入 支持 不同 post set 成像

模塊

使用模塊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__

。創建實例時就須要傳入arg1arg2參數。__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 MethodTypes.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)返回值是typetype(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