Python學習筆記(四)

1. 面向對象編程(OOP)

面向對象編程,簡稱OOP,是一種程序設計思想。OOP把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。
在Python中,全部數據類型均可以視爲對象,固然也能夠自定義對象。自定義的對象數據類型就是面向對象中的(Class)的概念。git

 
 
 
 
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))

給對象發消息其實是調用對象對應的關聯函數,稱之爲對象的方法(Method)編程

1.1 類和實例

面向對象最重要的概念就是類(Class)和實例(Insance)。
在Python中定義類用class關鍵字,class後緊接着是類名,類名一般是大寫開頭的單詞,緊接着是Object。
因爲類能夠起到模板的做用,所以,在建立實類的時候,把一些咱們認爲是必須綁定的屬性強制填寫進去。經過定義一個特殊的_init_方法。api

 
 
 
 
class Student(object): def __init__(self, name, score): # 注意_init_第一個參數永遠是self,表示建立實例的自己 self.name = name self.score = score

數據封裝:
封裝數據的函數是和類自己關聯起來的,咱們稱之爲類的方法:app

 
 
 
 
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): # 除了第一個參數是self外,其餘和普通函數同樣 print('%s: %s' % (self.name, self.score))

1.2 訪問限制

若是讓內部屬性不被外部訪問修改,能夠把屬性的名稱的前面加上兩個下劃線_,在Python中,實例的變量名若是以__開頭,就變成了一個私有變量(private),只有內部能夠訪問,外部不能訪問。ssh

 
 
 
 
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))

若是外部代碼想獲取和修改name和score,能夠給類增長get_score和set_score這樣的方法。ide

 
 
 
 
class Student(object): ... def get_score(self): return self.__score def set_score(self, score): self.__score = score

在Python中,以雙下劃線_開頭和以雙下劃線_結束的變量名,相似__xx__這樣的是特殊變量,特殊變量是能夠直接訪問的,不是private變量。
以一個下劃線開頭的變量,好比_name,這樣的實例變量是能夠直接訪問的,可是按照約定成俗的規定,意思就是」雖然我能夠被直接訪問,可是,請把我視爲私有變量,不要隨意訪問」。
雙下劃線開頭的變量,好比__name也不是必定不能被外部直接訪問,能夠經過_Student__name來訪問__name變量。函數

1.3 繼承和多態

在OOP程序設計中,當咱們定義一個Class的時候,能夠從某個現有的Class繼承,新的Class類稱爲子類,而被繼承的類稱爲基類,父類或者超類。學習

 
 
 
 
# 基類class Animal(object): def run(self): print('Animal is running...')# 子類class Dog(Animal): pass

繼承的好處:網站

  1. 子類能夠繼承父類全部的方法。
  2. 子類能夠本身增長方法。ui

    當子類和父類同時擁有相同的方法時,子類覆蓋了父類的方法,執行子類的方法,稱之爲多態
    多態的好處,就是著名的開閉原則。對拓展開放,對修改關閉。
    靜態語言VS動態語言
    對於靜態語言(Java)來講,若是須要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,不然,則沒法調用run()方法。
    對於動態語言(Python)來講,則不必定須要傳入Animal類,咱們只須要保證傳入的對象一個run()方法就行。

 
 
 
 
class Timer(object): def run(self): print('Start...')

1.4 獲取對象信息

  1. 判斷對象類型,使用type()。
  2. 對於class繼承關係來講,使用type()就不方便,要判斷class的類型,可使用isinstance()函數。能用type()判斷的基本類型也能夠用isinstance()判斷。
  3. 若是要得到一個對象的全部屬性和方法,可使用dir()函數,返回一個包含字符串的list。
 
 
 
 
# type()判斷對象類型>>> type(abs)<class 'builtin_function_or_method'>>>> type(a)<class '__main__.Animal'># isinstance()判斷對象類型>>> isinstance([1, 2, 3], (list, tuple))True>>> isinstance((1, 2, 3), (list, tuple))True# dir()獲取對象的全部屬性和方法>>> dir('ABC')['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

1.5 實例屬性和類屬性

因爲Python是動態語言,根據類建立的實例能夠綁定任意屬性。
在編寫程序的時候,千萬不要把實例屬性和類屬性使用相同的名字,由於相同名稱的實例屬性將屏蔽掉類屬性,當刪除實例屬性的時候,再使用相同的名稱,訪問到的將是類屬性。

 
 
 
 
>>> class Student(object):... name = 'Student'...>>> s = Student() # 建立實例s>>> print(s.name) # 打印name屬性,由於實例並無name屬性,因此會繼續查找class的name屬性Student>>> print(Student.name) # 打印類的name屬性Student>>> s.name = 'Michael' # 給實例綁定name屬性>>> print(s.name) # 因爲實例屬性優先級比類屬性高,所以,它會屏蔽掉類的name屬性Michael>>> print(Student.name) # 可是類屬性並未消失,用Student.name仍然能夠訪問Student>>> del s.name # 若是刪除實例的name屬性>>> print(s.name) # 再次調用s.name,因爲實例的name屬性沒有找到,類的name屬性就顯示出來了Student

2. 面向對象高級編程

2.1 使用_slots_

爲了限制實例的屬性,Python容許在定義class的時候,定義一個特殊的_slots_變量。

 
 
 
 
class Student(object): __slots__ = ('name', 'age') # 用tuple定義容許綁定的屬性名稱
 
 
 
 
>>> s = Student() # 建立新的實例>>> s.name = 'Michael' # 綁定屬性'name'>>> s.age = 25 # 綁定屬性'age'>>> s.score = 99 # 綁定屬性'score'Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'Student' object has no attribute 'score'

使用_slots_要注意,_slots_定義的屬性僅對當前類實例起做用,對繼承的子類是不起做用的。

2.2 使用@property

在綁定屬性的時候,咱們不能直接把屬性暴露出去,爲了限制屬性的範圍,能夠經過get和set方法檢查參數。
Python內置的@property裝飾器就是負責把一個方法變成屬性調用。

 
 
 
 
class Student(object): @property def score(self): return self._score @score.setter # 只定義getter方法,不定義setter方法就代表該屬性只讀 def score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value

2.3 多重繼承

經過多重繼承,一個子類就能夠同時得到多個子類的全部功能。
在設計類的繼承關係時,一般,主線是單一繼承下來的,若是須要混入」額外」的功能,一般就須要多重繼承來實現。這種設計稱之爲」MixIn」。
咱們不須要負責龐大的繼承鏈,只有選擇組合不一樣類的功能,就能夠快速構造出所需的子類。

2.4 定製類

_str_
print打印實例的時候,會出現一堆相似<main.Student object at 0x109afb190>,爲了打印好看,咱們只須要定義一個_str_方法。可是直接打印的時候仍是會出現相似的問題,那麼再定義一個_repr_()就會與_str_()效果同樣。

 
 
 
 
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__

_iter_
若是一個類想被用於for…in…循環,相似list和tuple這樣,就必須實現一個_iter_方法,該方法返回一個迭代對象。

 
 
 
 
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化兩個計數器a,b def __iter__(self): return self # 實例自己就是迭代對象,故返回本身 def __next__(self): self.a, self.b = self.b, self.a + self.b # 計算下一個值 if self.a > 100000: # 退出循環的條件 raise StopIteration(); return self.a # 返回下一個值
 
 
 
 
>>> for n in Fib():... print(n)...11235...4636875025

_getitem_
按照下標取出元素,實現切片的功能,就要實現_getitem_方法。

 
 
 
 
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 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

_getattr_
正常狀況下,當咱們調用類的方法或屬性的時候,若是不存在就會報錯,爲了不這個錯誤,Python中的_getattr_()方法能動態返回一個屬性。

 
 
 
 
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99

_call_
一個對象的實例能夠有本身的屬性和方法,當咱們調用實例方法時,咱們用instance.method()來調用,可是任何類只須要定義一個_call_()方法,就能夠直接對實例進行調用。

 
 
 
 
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()函數,咱們就能夠斷定一個對象是不是」可調用」對象。

2.5 使用枚舉類

當咱們須要定義常量的時候,一個辦法就是用大寫常量名經過整數定義,更好的辦法是爲這樣的枚舉類型定義一個class類型,而後,每一個常量是class的惟一的實例。Python提供了Enum類來實現這個功能。

 
 
 
 
from enum import EnumMonth = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))# 這樣咱們就得到了Month類型的枚舉類,能夠直接使用Month.Jan來引用一個變量,或者枚舉它的全部成員。for name, member in Month.__members__.items(): print(name, '=>', member, ',', member.value)

若是須要更精確的控制枚舉類型,能夠從Enum派生出自定義類。

 
 
 
 
from enum import Enum, unique@unique # @unique裝飾器能夠幫助咱們檢查保證沒有重複值class Weekday(Enum): Sun = 0 # Sun的value被設定爲0 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6 # 訪問枚舉類型的若干種方法 >>> day1 = Weekday.Mon>>> print(day1)Weekday.Mon>>> print(Weekday.Tue)Weekday.Tue>>> print(Weekday['Tue'])Weekday.Tue>>> print(Weekday.Tue.value)2>>> print(day1 == Weekday.Mon)True>>> print(day1 == Weekday.Tue)False>>> print(Weekday(1))Weekday.Mon>>> print(day1 == Weekday(1))True>>> Weekday(7)Traceback (most recent call last): ...ValueError: 7 is not a valid Weekday>>> for name, member in Weekday.__members__.items():... print(name, '=>', member)...Sun => Weekday.SunMon => Weekday.MonTue => Weekday.TueWed => Weekday.WedThu => Weekday.ThuFri => Weekday.FriSat => Weekday.Sat # 既能夠根據成員名稱引用枚舉常量,又能夠根據value的值得到枚舉常量。

2.6 使用元類

class的定義是運行時動態建立的,而建立class的方法就是使用type()函數。
type()函數能夠查看一個類的類型或變量的類型,既能夠返回一個對象的類型,又能夠建立出新的類型。

 
 
 
 
>>> def fn(self, name='world'): # 先定義函數... print('Hello, %s.' % name)...>>> Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class>>> h = Hello()>>> h.hello()Hello, world.>>> print(type(Hello))<class 'type'>>>> print(type(h))<class '__main__.Hello'>

除了使用type()動態建立類之外,要控制類的建立行爲,還可使用metaclass。
metaclass,直譯爲元類,簡單的解釋就是,當咱們定義了類以後,就能夠根據類建立出實例,因此,先定義類,而後建立實例。
先定義metaclass,就能夠建立類,最後建立實例。


感謝廖雪峯的官方網站提供的教程。Python學習筆記系列都基於廖老師的教程。



相關文章
相關標籤/搜索