面向對象編程,簡稱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)編程
面向對象最重要的概念就是類(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))
若是讓內部屬性不被外部訪問修改,能夠把屬性的名稱的前面加上兩個下劃線_,在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變量。函數
在OOP程序設計中,當咱們定義一個Class的時候,能夠從某個現有的Class繼承,新的Class類稱爲子類,而被繼承的類稱爲基類,父類或者超類。學習
# 基類class Animal(object): def run(self): print('Animal is running...')# 子類class Dog(Animal): pass
繼承的好處:網站
子類能夠本身增長方法。ui
當子類和父類同時擁有相同的方法時,子類覆蓋了父類的方法,執行子類的方法,稱之爲多態。
多態的好處,就是著名的開閉原則。對拓展開放,對修改關閉。
靜態語言VS動態語言
對於靜態語言(Java)來講,若是須要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,不然,則沒法調用run()方法。
對於動態語言(Python)來講,則不必定須要傳入Animal類,咱們只須要保證傳入的對象一個run()方法就行。
class Timer(object): def run(self): print('Start...')
# 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']
因爲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
爲了限制實例的屬性,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_定義的屬性僅對當前類實例起做用,對繼承的子類是不起做用的。
在綁定屬性的時候,咱們不能直接把屬性暴露出去,爲了限制屬性的範圍,能夠經過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
經過多重繼承,一個子類就能夠同時得到多個子類的全部功能。
在設計類的繼承關係時,一般,主線是單一繼承下來的,若是須要混入」額外」的功能,一般就須要多重繼承來實現。這種設計稱之爲」MixIn」。
咱們不須要負責龐大的繼承鏈,只有選擇組合不一樣類的功能,就能夠快速構造出所需的子類。
_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()函數,咱們就能夠斷定一個對象是不是」可調用」對象。
當咱們須要定義常量的時候,一個辦法就是用大寫常量名經過整數定義,更好的辦法是爲這樣的枚舉類型定義一個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的值得到枚舉常量。
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學習筆記系列都基於廖老師的教程。