Python從設計之初就已是一門面向對象的語言,正由於如此,在Python中建立一個類和對象是很容易的。本章節咱們將詳細介紹Python的面向對象編程。html
若是你之前沒有接觸過面向對象的編程語言,那你可能須要先了解一些面嚮對象語言的一些基本特徵,在頭腦裏頭造成一個基本的面向對象的概念,這樣有助於你更容易的學習Python的面向對象編程。python
接下來咱們先來簡單的瞭解下面向對象的一些基本特徵。正則表達式
和其它編程語言相比,Python 在儘量不增長新的語法和語義的狀況下加入了類機制。算法
Python中的類提供了面向對象編程的全部基本功能:類的繼承機制容許多個基類,派生類能夠覆蓋基類中的任何方法,方法中能夠調用基類中的同名方法。編程
對象能夠包含任意數量和類型的數據。bash
語法格式以下:服務器
class ClassName:
<statement-1>
.
.
.
<statement-N>複製代碼
類實例化後,可使用其屬性,實際上,建立一個類以後,能夠經過類名訪問其屬性。網絡
類對象支持兩種操做:屬性引用和實例化。數據結構
屬性引用使用和 Python 中全部的屬性引用同樣的標準語法:obj.name。閉包
類對象建立後,類命名空間中全部的命名都是有效屬性名。因此若是類定義是這樣:
實例(Python 3.0+)
class MyClass:
"""一個簡單的類實例"""
i = 12345
def f(self):
return 'hello world'
# 實例化類
x = MyClass()
# 訪問類的屬性和方法
print("MyClass 類的屬性 i 爲:", x.i)
print("MyClass 類的方法 f 輸出爲:", x.f())複製代碼
以上建立了一個新的類實例並將該對象賦給局部變量 x,x 爲空的對象。
執行以上程序輸出結果爲:
MyClass 類的屬性 i 爲: 12345
MyClass 類的方法 f 輸出爲: hello world複製代碼
類有一個名爲 __init__() 的特殊方法(構造方法),該方法在類實例化時會自動調用,像下面這樣:
def __init__(self):
self.data = []複製代碼
類定義了 __init__() 方法,類的實例化操做會自動調用 __init__() 方法。以下實例化類 MyClass,對應的 __init__() 方法就會被調用:
x = MyClass()複製代碼
固然, __init__() 方法能夠有參數,參數經過 __init__() 傳遞到類的實例化操做上。例如:
實例(Python 3.0+)
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 輸出結果:3.0 -4.5複製代碼
self表明類的實例,而非類
類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是 self。
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()複製代碼
以上實例執行結果爲:
<__main__.Test instance at 0x100771878>
__main__.Test複製代碼
從執行結果能夠很明顯的看出,self 表明的是類的實例,表明當前對象的地址,而 self.class 則指向類。
self 不是 python 關鍵字,咱們把他換成 runoob 也是能夠正常執行的:
class Test:
def prt(runoob):
print(runoob)
print(runoob.__class__)
t = Test()
t.prt()複製代碼
以上實例執行結果爲:
<__main__.Test instance at 0x100771878>
__main__.Test複製代碼
在類的內部,使用 def 關鍵字來定義一個方法,與通常函數定義不一樣,類方法必須包含參數 self, 且爲第一個參數,self 表明的是類的實例。
實例(Python 3.0+)
#類定義
class people:
#定義基本屬性
name = ''
age = 0
#定義私有屬性,私有屬性在類外部沒法直接進行訪問
__weight = 0
#定義構造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 說: 我 %d 歲。" %(self.name,self.age))
# 實例化類
p = people('runoob',10,30)
p.speak()複製代碼
執行以上程序輸出結果爲:
runoob 說: 我 10 歲。複製代碼
Python 一樣支持類的繼承,若是一種語言不支持繼承,類就沒有什麼意義。派生類的定義以下所示:
class DerivedClassName(BaseClassName1):
<statement-1>
.
.
.
<statement-N>複製代碼
須要注意圓括號中基類的順序,如果基類中有相同的方法名,而在子類使用時未指定,python從左至右搜索 即方法在子類中未找到時,從左到右查找基類中是否包含方法。
BaseClassName(示例中的基類名)必須與派生類定義在一個做用域內。除了類,還能夠用表達式,基類定義在另外一個模塊中時這一點很是有用:
class DerivedClassName(modname.BaseClassName):複製代碼
實例(Python 3.0+)
#類定義
class people:
#定義基本屬性
name = ''
age = 0
#定義私有屬性,私有屬性在類外部沒法直接進行訪問
__weight = 0
#定義構造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 說: 我 %d 歲。" %(self.name,self.age))
#單繼承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#調用父類的構函
people.__init__(self,n,a,w)
self.grade = g
#覆寫父類的方法
def speak(self):
print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak(複製代碼
執行以上程序輸出結果爲:
ken 說: 我 10 歲了,我在讀 3 年級複製代碼
Python一樣有限的支持多繼承形式。多繼承的類定義形以下例:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>複製代碼
須要注意圓括號中父類的順序,如果父類中有相同的方法名,而在子類使用時未指定,python從左至右搜索 即方法在子類中未找到時,從左到右查找父類中是否包含方法。
實例(Python 3.0+)
#!/usr/bin/python3
#類定義
class people:
#定義基本屬性
name = ''
age = 0
#定義私有屬性,私有屬性在類外部沒法直接進行訪問
__weight = 0
#定義構造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 說: 我 %d 歲。" %(self.name,self.age))
#單繼承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#調用父類的構函
people.__init__(self,n,a,w)
self.grade = g
#覆寫父類的方法
def speak(self):
print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
#另外一個類,多重繼承以前的準備
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一個演說家,我演講的主題是 %s"%(self.name,self.topic))
#多重繼承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默認調用的是在括號中排前地父類的方法複製代碼
執行以上程序輸出結果爲:
我叫 Tim,我是一個演說家,我演講的主題是 Python複製代碼
若是你的父類方法的功能不能知足你的需求,你能夠在子類重寫你父類的方法,實例以下:
實例(Python 3.0+)
#!/usr/bin/python3
class Parent: # 定義父類
def myMethod(self):
print ('調用父類方法')
class Child(Parent): # 定義子類
def myMethod(self):
print ('調用子類方法')
c = Child() # 子類實例
c.myMethod() # 子類調用重寫方法
super(Child,c).myMethod() #用子類對象調用父類已被覆蓋的方法複製代碼
super() 函數是用於調用父類(超類)的一個方法。
執行以上程序輸出結果爲:
調用子類方法
調用父類方法複製代碼
更多文檔:
類的私有屬性
__private_attrs:兩個下劃線開頭,聲明該屬性爲私有,不能在類的外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs。
類的方法
在類的內部,使用 def 關鍵字來定義一個方法,與通常函數定義不一樣,類方法必須包含參數 self,且爲第一個參數,self 表明的是類的實例。
self 的名字並非規定死的,也可使用 this,可是最好仍是按照約定是用 self。
類的私有方法
__private_method:兩個下劃線開頭,聲明該方法爲私有方法,只能在類的內部調用 ,不能在類的外部調用。self.__private_methods。
類的私有屬性實例以下:
實例(Python 3.0+)
class JustCounter:
__secretCount = 0 # 私有變量
publicCount = 0 # 公開變量
def count(self):
self.__secretCount += 1
self.publicCount += 1
print (self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print (counter.publicCount)
print (counter.__secretCount) # 報錯,實例不能訪問私有變量複製代碼
執行以上程序輸出結果爲:
1
2
2
Traceback (most recent call last):
File "test.py", line 16, in <module>
print (counter.__secretCount) # 報錯,實例不能訪問私有變量
AttributeError: 'JustCounter' object has no attribute '__secretCount'複製代碼
類的私有方法實例以下:
實例(Python 3.0+)
class Site:
def __init__(self, name, url):
self.name = name # public
self.__url = url # private
def who(self):
print('name : ', self.name)
print('url : ', self.__url)
def __foo(self): # 私有方法
print('這是私有方法')
def foo(self): # 公共方法
print('這是公共方法')
self.__foo()
x = Site('菜鳥教程', 'www.runoob.com')
x.who() # 正常輸出
x.foo() # 正常輸出
x.__foo() # 報錯複製代碼
以上實例執行結果:
類的專有方法:
運算符重載
Python一樣支持運算符重載,咱們能夠對類的專有方法進行重載,實例以下:
實例(Python 3.0+)
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)複製代碼
以上代碼執行結果以下所示:
Vector(7,8)複製代碼
先看看官方文檔的一段話:
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空間(Namespace)是從名稱到對象的映射,大部分的命名空間都是經過 Python 字典來實現的。
命名空間提供了在項目中避免名字衝突的一種方法。各個命名空間是獨立的,沒有任何關係的,因此一個命名空間中不能有重名,但不一樣的命名空間是能夠重名而沒有任何影響。
咱們舉一個計算機系統中的例子,一個文件夾(目錄)中能夠包含多個文件夾,每一個文件夾中不能有相同的文件名,但不一樣文件夾中的文件能夠重名。
通常有三種命名空間:
命名空間查找順序:
假設咱們要使用變量 runoob,則 Python 的查找順序爲:局部的命名空間去 -> 全局命名空間 -> 內置命名空間。
若是找不到變量 runoob,它將放棄查找並引起一個 NameError 異常:
NameError: name 'runoob' is not defined。複製代碼
命名空間的生命週期:
命名空間的生命週期取決於對象的做用域,若是對象執行完成,則該命名空間的生命週期就結束。
所以,咱們沒法從外部命名空間訪問內部命名空間的對象。
實例
# var1 是全局名稱
var1 = 5
def some_func():
# var2 是局部名稱
var2 = 6
def some_inner_func():
# var3 是內嵌的局部名稱
var3 = 7複製代碼
以下圖所示,相同的對象名稱能夠存在於多個命名空間中。
A scope is a textual region of a Python program where a namespace is directly accessible. "Directly accessible" here means that an unqualified reference to a name attempts to find the name in the namespace.
做用域就是一個 Python 程序能夠直接訪問命名空間的正文區域。
在一個 python 程序中,直接訪問一個變量,會從內到外依次訪問全部的做用域直到找到,不然會報未定義的錯誤。
Python 中,程序的變量並非在哪一個位置均可以訪問的,訪問權限決定於這個變量是在哪裏賦值的。
變量的做用域決定了在哪一部分程序能夠訪問哪一個特定的變量名稱。Python的做用域一共有4種,分別是:
有四種做用域:
規則順序: L –> E –> G –>gt; B。
在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,再者去內置中找。
g_count = 0 # 全局做用域
def outer():
o_count = 1 # 閉包函數外的函數中
def inner():
i_count = 2 # 局部做用域複製代碼
內置做用域是經過一個名爲 builtin 的標準模塊來實現的,可是這個變量名自身並無放入內置做用域內,因此必須導入這個文件纔可以使用它。在Python3.0中,可使用如下的代碼來查看到底預約義了哪些變量:
>>> import builtins
>>> dir(builtins)複製代碼
Python 中只有模塊(module),類(class)以及函數(def、lambda)纔會引入新的做用域,其它的代碼塊(如 if/elif/else/、try/except、for/while等)是不會引入新的做用域的,也就是說這些語句內定義的變量,外部也能夠訪問,以下代碼:
>>> if True:
... msg = 'I am from Runoob'
...
>>> msg
'I am from Runoob'
>>> 複製代碼
實例中 msg 變量定義在 if 語句塊中,但外部仍是能夠訪問的。
若是將 msg 定義在函數中,則它就是局部變量,外部不能訪問:
>>> def test():
... msg_inner = 'I am from Runoob'
...
>>> msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
>>> 複製代碼
從報錯的信息上看,說明了 msg_inner 未定義,沒法使用,由於它是局部變量,只有在函數內可使用。
全局變量和局部變量
定義在函數內部的變量擁有一個局部做用域,定義在函數外的擁有全局做用域。
局部變量只能在其被聲明的函數內部訪問,而全局變量能夠在整個程序範圍內訪問。調用函數時,全部在函數內聲明的變量名稱都將被加入到做用域中。以下實例:
實例(Python 3.0+)
以上實例輸出結果:
函數內是局部變量 : 30
函數外是全局變量 : 0複製代碼
global 和 nonlocal關鍵字
當內部做用域想修改外部做用域的變量時,就要用到global和nonlocal關鍵字了。
如下實例修改全局變量 num:
實例(Python 3.0+)
#!/usr/bin/python3
num = 1
def fun1():
global num # 須要使用 global 關鍵字聲明
print(num)
num = 123
print(num)
fun1()
print(num)複製代碼
以上實例輸出結果:
1
123
123複製代碼
若是要修改嵌套做用域(enclosing 做用域,外層非全局做用域)中的變量則須要 nonlocal 關鍵字了,以下實例:
實例(Python 3.0+)
#!/usr/bin/python3
def outer():
num = 10
def inner():
nonlocal num # nonlocal關鍵字聲明
num = 100
print(num)
inner()
print(num)
outer()複製代碼
以上實例輸出結果:
100
100複製代碼
另外有一種特殊狀況,假設下面這段代碼被運行:
實例(Python 3.0+)
以上程序執行,報錯信息以下:
Traceback (most recent call last):
File "test.py", line 7, in <module>
test()
File "test.py", line 5, in test
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment複製代碼
錯誤信息爲局部做用域引用錯誤,由於 test 函數中的 a 使用的是局部,未定義,沒法修改。
修改 a 爲全局變量,經過函數參數傳遞,能夠正常執行輸出結果爲:
實例(Python 3.0+)
a = 10
def test(a):
a = a + 1
print(a)
test(a)複製代碼
執行輸出結果爲:
11複製代碼
os模塊提供了很多與操做系統相關聯的函數。
>>> import os
>>> os.getcwd() # 返回當前的工做目錄
'C:\\Python34'
>>> os.chdir('/server/accesslogs') # 修改當前的工做目錄
>>> os.system('mkdir today') # 執行系統命令 mkdir
0複製代碼
建議使用 "import os" 風格而非 "from os import *"。這樣能夠保證隨操做系統不一樣而有所變化的 os.open() 不會覆蓋內置函數 open()。
在使用 os 這樣的大型模塊時內置的 dir() 和 help() 函數很是有用:
>>> import os
>>> dir(os)
<returns a list of all module functions>
>>> help(os)
<returns an extensive manual page created from the module's docstrings>複製代碼
針對平常的文件和目錄管理任務,:mod:shutil 模塊提供了一個易於使用的高級接口:
>>> import shutil
>>> shutil.copyfile('data.db', 'archive.db')
>>> shutil.move('/build/executables', 'installdir')複製代碼
glob模塊提供了一個函數用於從目錄通配符搜索中生成文件列表:
>>> import glob
>>> glob.glob('*.py')
['primes.py', 'random.py', 'quote.py']複製代碼
通用工具腳本常常調用命令行參數。這些命令行參數以鏈表形式存儲於 sys 模塊的 argv 變量。例如在命令行中執行 "python demo.py one two three" 後能夠獲得如下輸出結果:
>>> import sys
>>> print(sys.argv)
['demo.py', 'one', 'two', 'three']複製代碼
sys 還有 stdin,stdout 和 stderr 屬性,即便在 stdout 被重定向時,後者也能夠用於顯示警告和錯誤信息。
>>> sys.stderr.write('Warning, log file not found starting a new one\n')
Warning, log file not found starting a new one複製代碼
大多腳本的定向終止都使用 "sys.exit()"。
re模塊爲高級字符串處理提供了正則表達式工具。對於複雜的匹配和處理,正則表達式提供了簡潔、優化的解決方案:
>>> import re
>>> re.findall(r'\bf[a-z]*', 'which foot or hand fell fastest')
['foot', 'fell', 'fastest']
>>> re.sub(r'(\b[a-z]+) \1', r'\1', 'cat in the the hat')
'cat in the hat'複製代碼
若是隻須要簡單的功能,應該首先考慮字符串方法,由於它們很是簡單,易於閱讀和調試:
>>> 'tea for too'.replace('too', 'two')
'tea for two'複製代碼
5. 數學
math模塊爲浮點運算提供了對底層C函數庫的訪問:
>>> import math
>>> math.cos(math.pi / 4)
0.70710678118654757
>>> math.log(1024, 2)
10.0複製代碼
random提供了生成隨機數的工具。
>>> import random
>>> random.choice(['apple', 'pear', 'banana'])
'apple'
>>> random.sample(range(100), 10) # sampling without replacement
[30, 83, 16, 4, 8, 81, 41, 50, 18, 33]
>>> random.random() # random float
0.17970987693706186
>>> random.randrange(6) # random integer chosen from range(6)
4複製代碼
有幾個模塊用於訪問互聯網以及處理網絡通訊協議。其中最簡單的兩個是用於處理從 urls 接收的數據的 urllib.request 以及用於發送電子郵件的 smtplib:
>>> from urllib.request import urlopen
>>> for line in urlopen('http://tycho.usno.navy.mil/cgi-bin/timer.pl'):
... line = line.decode('utf-8') # Decoding the binary data to text.
... if 'EST' in line or 'EDT' in line: # look for Eastern Time
... print(line)
<BR>Nov. 25, 09:43:32 PM EST
>>> import smtplib
>>> server = smtplib.SMTP('localhost')
>>> server.sendmail('soothsayer@example.org', 'jcaesar@example.org',
... """To: jcaesar@example.org ... From: soothsayer@example.org ... ... Beware the Ides of March. ... """)
>>> server.quit()複製代碼
注意第二個例子須要本地有一個在運行的郵件服務器。
datetime模塊爲日期和時間處理同時提供了簡單和複雜的方法。
支持日期和時間算法的同時,實現的重點放在更有效的處理和格式化輸出。
該模塊還支持時區處理:
>>> # dates are easily constructed and formatted
>>> from datetime import date
>>> now = date.today()
>>> now
datetime.date(2003, 12, 2)
>>> now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")
'12-02-03. 02 Dec 2003 is a Tuesday on the 02 day of December.'
>>> # dates support calendar arithmetic
>>> birthday = date(1964, 7, 31)
>>> age = now - birthday
>>> age.days
14368複製代碼
如下模塊直接支持通用的數據打包和壓縮格式:zlib,gzip,bz2,zipfile,以及 tarfile。
>>> import zlib
>>> s = b'witch which has which witches wrist watch'
>>> len(s)
41
>>> t = zlib.compress(s)
>>> len(t)
37
>>> zlib.decompress(t)
b'witch which has which witches wrist watch'
>>> zlib.crc32(s)
226805979複製代碼
有些用戶對了解解決同一問題的不一樣方法之間的性能差別很感興趣。Python 提供了一個度量工具,爲這些問題提供了直接答案。
例如,使用元組封裝和拆封來交換元素看起來要比使用傳統的方法要誘人的多,timeit 證實了現代的方法更快一些。
>>> from timeit import Timer
>>> Timer('t=a; a=b; b=t', 'a=1; b=2').timeit()
0.57535828626024577
>>> Timer('a,b = b,a', 'a=1; b=2').timeit()
0.54962537085770791複製代碼
相對於 timeit 的細粒度,:mod:profile 和 pstats 模塊提供了針對更大代碼塊的時間度量工具。
開發高質量軟件的方法之一是爲每個函數開發測試代碼,而且在開發過程當中常常進行測試
doctest模塊提供了一個工具,掃描模塊並根據程序中內嵌的文檔字符串執行測試。
測試構造如同簡單的將它的輸出結果剪切並粘貼到文檔字符串中。
經過用戶提供的例子,它強化了文檔,容許 doctest 模塊確認代碼的結果是否與文檔一致:
def average(values):
"""Computes the arithmetic mean of a list of numbers. >>> print(average([20, 30, 70])) 40.0 """
return sum(values) / len(values)
import doctest
doctest.testmod() # 自動驗證嵌入測試複製代碼
unittest模塊不像 doctest模塊那麼容易使用,不過它能夠在一個獨立的文件裏提供一個更全面的測試集:
import unittest
class TestStatisticalFunctions(unittest.TestCase):
def test_average(self):
self.assertEqual(average([20, 30, 70]), 40.0)
self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
self.assertRaises(ZeroDivisionError, average, [])
self.assertRaises(TypeError, average, 20, 30, 70)
unittest.main() # Calling from the command line invokes all tests複製代碼