Python基礎入門筆記(二)

前言

本文主要爲 Python基礎入門筆記(一)內容的補充。html

1、迭代器和生成器

1.1 Python迭代器

迭代器是一個能夠記住遍歷的位置的對象。java

迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。python

迭代器只能往前不會後退。linux

迭代器有兩個基本的方法:iter()next(),且字符串、列表或元組對象均可用於建立迭代器,迭代器對象可使用常規 for 語句進行遍歷,也可使用 next() 函數來遍歷。程序員

具體的實例:正則表達式

# 一、字符創建立迭代器對象
str1 = 'jaybo'
iter1 = iter ( str1 )

# 二、list對象建立迭代器
list1 = [1,2,3,4]
iter2 = iter ( list1 )

# 三、tuple(元祖) 對象建立迭代器
tuple1 = ( 1,2,3,4 )
iter3 = iter ( tuple1 )

# for 循環遍歷迭代器對象
for x in iter1 :
    print ( x , end = ' ' )

print('\n------------------------')

# next() 函數遍歷迭代器
while True :
    try :
        print ( next ( iter3 ) )
    except StopIteration :
        break
複製代碼

最後輸出的結果:編程

j a y b o
------------------------
1
2
3
4
複製代碼

list(列表)生成式:數據結構

語法爲:多線程

[expr for iter_var in iterable] 
[expr for iter_var in iterable if cond_expr]
複製代碼

第一種語法:首先迭代 iterable 裏全部內容,每一次迭代,都把 iterable 裏相應內容放到iter_var 中,再在表達式中應用該 iter_var 的內容,最後用表達式的計算值生成一個列表。閉包

第二種語法:加入了判斷語句,只有知足條件的內容才把 iterable 裏相應內容放到 iter_var 中,再在表達式中應用該 iter_var 的內容,最後用表達式的計算值生成一個列表。

實例,用一句代碼打印九九乘法表:

print('\n'.join([' '.join ('%dx%d=%2d' % (x,y,x*y)  for x in range(1,y+1)) for y in range(1,10)]))
複製代碼

輸出結果:

1x1= 1
1x2= 2 2x2= 4
1x3= 3 2x3= 6 3x3= 9
1x4= 4 2x4= 8 3x4=12 4x4=16
1x5= 5 2x5=10 3x5=15 4x5=20 5x5=25
1x6= 6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7= 7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8= 8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9= 9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
複製代碼

1.2 生成器

在 Python 中,使用了 yield 的函數被稱爲生成器(generator)。

跟普通函數不一樣的是,生成器是一個返回迭代器的函數,只能用於迭代操做,更簡單點理解生成器就是一個迭代器。

在調用生成器運行的過程當中,每次遇到 yield 時函數會暫停並保存當前全部的運行信息,返回 yield 的值。並在下一次執行 next() 方法時從當前位置繼續運行。

①建立:

生成器的建立:最簡單最簡單的方法就是把一個列表生成式的 [] 改爲 ()

gen= (x * x for x in range(10))
print(gen)
複製代碼

輸出結果:

generator object  at 0x0000000002734A40
複製代碼

建立 List 和 generator 的區別僅在於最外層的 [] 和 () 。可是生成器並不真正建立數字列表, 而是返回一個生成器,這個生成器在每次計算出一個條目後,把這個條目「產生」 ( yield ) 出來。 生成器表達式使用了「惰性計算」 ( lazy evaluation,也有翻譯爲「延遲求值」,我覺得這種按需調用 call by need 的方式翻譯爲惰性更好一些),只有在檢索時才被賦值( evaluated ),因此在列表比較長的狀況下使用內存上更有效。

②以函數形式實現生成器:

其實生成器也是一種迭代器,可是你只能對其迭代一次。這是由於它們並無把全部的值存在內存中,而是在運行時生成值。你經過遍從來使用它們,要麼用一個「for」循環,要麼將它們傳遞給任意能夠進行迭代的函數和結構。並且實際運用中,大多數的生成器都是經過函數來實現的。

生成器和函數的不一樣:

函數是順序執行,遇到 return 語句或者最後一行函數語句就返回。而變成 generator 的函數,在每次調用 next() 的時候執行,遇到 yield語句返回,再次執行時從上次返回的 yield 語句處繼續執行。

舉個例子:

def odd():
    print ( 'step 1' )
    yield ( 1 )
    print ( 'step 2' )
    yield ( 3 )
    print ( 'step 3' )
    yield ( 5 )

o = odd()
print( next( o ) ) 
print( next( o ) ) 
print( next( o ) )
複製代碼

輸出結果:

step 1
1
step 2
3
step 3
5
複製代碼

能夠看到,odd 不是普通函數,而是 generator,在執行過程當中,遇到 yield 就中斷,下次又繼續執行。執行 3 次 yield 後,已經沒有 yield 能夠執行了,若是你繼續打印 print( next( o ) ) ,就會報錯的。因此一般在 generator 函數中都要對錯誤進行捕獲。

打印楊輝三角:

def triangles( n ):         # 楊輝三角形
    L = [1]
    while True:
        yield L
        L.append(0)
        L = [ L [ i -1 ] + L [ i ] for i in range (len(L))]

n= 0
for t in triangles( 10 ):   # 直接修改函數名便可運行
    print(t)
    n = n + 1
    if n == 10:
        break
複製代碼

輸出結果:

[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
複製代碼

1.3 延伸

①反向迭代

使用 Python 中有內置的函數 reversed()

要注意一點就是:反向迭代僅僅當對象的大小可預先肯定或者對象實現了 __reversed__() 的特殊方法時才能生效。 若是二者都不符合,那你必須先將對象轉換爲一個列表才行。

②同時迭代多個序列

爲了同時迭代多個序列,使用 zip() 函數,具體示例:

names = ['jaychou', 'zjl', '周杰倫']
ages = [18, 19, 20]
for name, age in zip(names, ages):
     print(name,age)
複製代碼

輸出的結果:

jaychou 18
zjl 19
周杰倫 20
複製代碼

其實 zip(a, b) 會生成一個可返回元組 (x, y) 的迭代器,其中 x 來自 a,y 來自 b。 一旦其中某個序列到底結尾,迭代宣告結束。 所以迭代長度跟參數中最短序列長度一致。注意理解這句話,也就是說若是 a , b 的長度不一致的話,以最短的爲標準,遍歷完後就結束。

2、模塊與包

2.1 模塊

2.1.1 什麼是模塊

在 Python 中,一個 .py 文件就稱之爲一個模塊(Module)。

咱們學習過函數,知道函數是實現一項或多項功能的一段程序 。其實模塊就是函數功能的擴展。爲何這麼說呢?那是由於模塊其實就是實現一項或多項功能的程序塊。

經過上面的定義,不難發現,函數和模塊都是用來實現功能的,只是模塊的範圍比函數廣,在模塊中,能夠有多個函數。

模塊的好處:

  • 模塊使用的最大好處是大大提升了代碼的可維護性,固然,還提升了代碼的複用性。

  • 使用模塊還能夠避免函數名和變量名衝突,相同名字的變量徹底能夠分別存在不一樣的模塊中。

    PS:可是也要注意,變量的名字儘可能不要與內置函數名字衝突。常見的內置函數:連接直達

再這也順帶先延伸下關於包的內容吧:

當編寫的模塊多了,模塊的名字重複的機率就增長了。如何解決這個問題呢?

Python 引入了按目錄來組織模塊,稱爲包(Package),好比:

extensions
├─ __init__.py
├─ dog.py
└─ cat.py
複製代碼

如今 dog.py 模塊的名字就變成了 extensions.dog

PS:請注意,每個 package 目錄下面都會有一個__init__.py 的文件,這個文件是必須有的,不然, Python 就把這個目錄當成普通目錄,而不是一個 package directory。

另外如何使用包中的模塊(Module)呢?以下編寫一個dog.py模塊:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'jack guo'

import sys

def shout():
    args = sys.argv
    if len(args)==1:
        print('Hello, I'm afei, welcome to world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
   else:
        print('Yes,sir')

if __name__=='__main__':
    shout()
複製代碼

解釋下:

第1行註釋可讓dog.py文件直接在linux上運行;
第2行註釋表示.py文件自己使用標準UTF-8編碼;
第4行表示模塊的文檔註釋;
第6行表示模塊的做者;

注意最後兩行代碼,當咱們調試dog.py時,shout()會調用,當在其餘模塊導入dog.py時,shout()不執行。
複製代碼

模塊的一種標準模板:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'jack guo'
複製代碼

以上是模塊的標準模板,固然,你也能夠不這樣作。

2.1.2 模塊的導入

導入模塊咱們使用關鍵字 import,語法格式以下:import module1[, module2[,... moduleN]

如:import math 導入標準模塊中的 math 模塊。

一個模塊只會被導入一次,無論你執行了多少次 import。這樣能夠防止導入模塊被一遍又一遍地執行。

Python 解釋器是怎樣找到對應的文件的呢?

搜索路徑:由一系列目錄名組成的。Python 解釋器就依次從這些目錄中去尋找所引入的模塊。這看起來很像環境變量,事實上,也能夠經過定義環境變量的方式來肯定搜索路徑。搜索路徑是在 Python 編譯或安裝的時候肯定的,安裝新的庫應該也會修改。搜索路徑被存儲在 sys 模塊中的 path 變量 。能夠打印出來:

import sys

print(sys.path)
複製代碼

2.1.3 導入模塊中的屬性和方法及調用

①導入模塊的方法

  • import 模塊名
  • import 模塊名 as 新名字
  • from 模塊名 import 函數名:大型項目中應儘可能避免使用此方法,除非你很是肯定不會形成命名衝突;它有一個好處就是可直接使用function()而不用加module.function()了。

PS1:導入模塊並不意味着在導入時執行某些操做,它們主要用於定義,好比變量、函數和類等。

PS2:可使用 from ··· import * 語句把某個模塊中的全部方法屬性都導入。

②模塊中變量、函數以及類的屬性和方法的調用

  1. module.variable
  2. module.function()
  3. module.class.variable

2.1.4 模塊的搜索路徑sys模塊的使用)

(1)程序所在目錄

(2)標準庫的安裝路徑

(3)操做系統環境變量 PYTHONPATH 指向的路徑

  • 得到當前 Python 搜索路徑的方法:

    import sys
    print(sys.path)
    複製代碼

    輸出:

    ['D:\\workspace_pycharm', 'D:\\workspace_pycharm', 'D:\\python-practice', 'D:\\devInstall\\devPython\\Python36\\python36.zip', 'D:\\devInstall\\devPython\\Python36\\DLLs', 'D:\\devInstall\\devPython\\Python36\\lib', 'D:\\devInstall\\devPython\\Python36', 'D:\\devInstall\\devPython\\Python36\\lib\\site-packages']
    複製代碼
  • sys 模塊的 argv 變量的用法:

    • sys 模塊有一個 argv(argument values) 變量,用 list 存儲了命令行的全部參數。
    • argv 至少有一個元素,由於第一個元素永遠都是.py文件的名稱。
    $ python solve.py 0    # 命令行語句
    # 得到argv變量的值
    sys.argv = ['solve.py', '0']
    sys.argv[0] = 'solve.py'
    sys.argv[1] = '0'
    複製代碼

2.1.5 主模塊和非主模塊

在 Python 函數中,若是一個函數調用了其餘函數完成一項功能,咱們稱這個函數爲主函數,若是一個函數沒有調用其餘函數,咱們稱這種函數爲非主函數。主模塊和非主模塊的定義也相似,若是一個模塊被直接使用,而沒有被別人調用,咱們稱這個模塊爲主模塊,若是一個模塊被別人調用,咱們稱這個模塊爲非主模塊。

怎麼區分主模塊和非主模塊呢?

能夠利用 __name__屬性。若是一個屬性的值是 __main__ ,那麼就說明這個模塊是主模塊,反之亦然。可是要注意了:這個 __main__ 屬性只是幫助咱們判斷是不是主模塊,並非說這個屬性決定他們是不是主模塊,決定是不是主模塊的條件只是這個模塊有沒有被人調用。以下:

if __name__ == '__main__':
    print('main')
else:
    print('not main')
複製代碼

若是輸出結果爲 main 則該模塊爲主模塊。

!!!補充: 在初學 Python 過程當中,總能遇到 if __name__ == 'main'語句,咱們正好來好好了解下。

先舉例子,假如 A.py 文件內容以下:

def sayhello():
    print('Hello!')
print('Hi!')
print(__name__)
複製代碼

輸出結果:

Hi!
__main__
複製代碼

結果很簡單,說明在運行 A.py 自己文件時,變量__name__的值是__main__

現有個 B.py 文件,代碼以下:

import A
A.sayhello()
print('End')
複製代碼

能夠看到,在 B.py 文件中,模塊 A 被導入,運行結果以下:

Hi!
A
Hello!
End
複製代碼

這裏涉及一些語句運行順序問題,在 B.py 文件中,模塊 A 中的 sayhello 函數是調用時才執行的,可是 A 中的 print 語句會馬上執行(由於沒有縮進,所以與def是平行級別的)。所以會先依次執行:

print('Hi!')
print(__name__)
複製代碼

而後執行:

A.sayhello()
print('End')
複製代碼

運行結果中Hi!對應於 A 模塊中的 print('Hi!'),而結果 A 對應於 print(__name__),可見當在 B 文件中調用 A 模塊時,變量__name__的值由__main__變爲了模塊 A 的名字。

這樣的好處是咱們能夠在 A.py 文件中進行一些測試,而避免在模塊調用的時候產生干擾,好比將 A 文件改成:

def sayhello():
    print('Hello!')
print('Hi!')
print(__name__)

if __name__ == '__main__':
    print('I am module A')
複製代碼

再次單獨運行 A.py 文件時,結果中會多出I am module A語句:

Hi!
__main__
I am module A
複製代碼

而運行 B.py 文件,即調用 A 模塊時,卻不會顯示該語句:

Hi!
A
Hello!
End
複製代碼

簡短總結下:

模塊屬性__name__,它的值由 Python 解釋器設定。若是 Python 程序是做爲主程序調用,其值就設爲__main__,若是是做爲模塊被其餘文件導入,它的值就是其文件名。

每一個模塊都有本身的私有符號表,全部定義在模塊裏面的函數把它當作全局符號表使用。

2.2 包

2.2.1 什麼是包

咱們本身在編寫模塊時,沒必要考慮名字會與其餘模塊衝突。可是也要注意,儘可能不要與內置函數名字衝突。可是這裏也有個問題,若是不一樣的人編寫的模塊名相同怎麼辦?爲了不模塊名衝突,Python 又引入了按目錄來組織模塊的方法,稱爲包(Package)。

仔細觀察的人,基本會發現,每個包目錄下面都會有一個 __init__.py 的文件。這個文件是必須的,不然,Python 就把這個目錄當成普通目錄,而不是一個包。 __init__.py 能夠是空文件,也能夠有 Python 代碼,由於 __init__.py 自己就是一個模塊,而它對應的模塊名就是它的包名。

2.2.2 包的定義和優勢

  • Python 把同類的模塊放在一個文件夾中統一管理,這個文件夾稱之爲一個
  • 若是把全部模塊都放在一塊兒顯然很差管理,而且有命名衝突的可能。
  • 包其實就是把模塊分門別類地存放在不一樣的文件夾,而後把各個文件夾的位置告訴Python。
  • Python 的包是按目錄來組織模塊的,也能夠有多級目錄,組成多級層次的包結構。

2.2.3 包的建立

  • 建立一個文件夾,用於存放相關的模塊,文件夾的名字即爲包的名字
  • 在文件夾中建立一個__init__.py的模塊文件,內容能夠爲空(普通文件夾和包的區別)。
  • 將相關模塊放入文件夾中

2.3.4 包的存放路徑及包中模塊的導入與調用

①包的存放

  • 若是不想把相關的模塊文件放在所建立的文件夾中,那麼最好的選擇就是:放在默認的site-packages文件夾裏,由於它就是用來存放你的模塊文件的。
  • sys.path.append(‘模塊的存放位置’)只是在運行時生效,運行結束後失效。
  • 將包的存放路徑加入用戶系統環境變量中的 PYTHONPYTH 中去,這樣在任何位置均可以調用包了(推薦)。

②包中模塊的導入

  1. import 包名.模塊名
  2. import 包名.模塊名 as 新名字
  3. from 包名 import 模塊名

③包中模塊的變量、函數以及類的屬性和方法的調用

  1. package.module.variable
  2. package.module.function()
  3. package.module.class.variable

2.3 做用域

學習過 Java 的同窗都知道,Java 的類裏面能夠給方法和屬性定義公共的( public )或者是私有的 ( private ),這樣作主要是爲了咱們但願有些函數和屬性能給別人使用或者只能內部使用。 經過學習 Python 中的模塊,其實和 Java 中的類類似,那麼咱們怎麼實如今一個模塊中,有的函數和變量給別人使用,有的函數和變量僅僅在模塊內部使用呢?

在 Python 中,是經過 _ 前綴來實現的。正常的函數和變量名是公開的(public),能夠被直接引用,好比:abcni12PI 等。

相似__xxx__這樣的變量是特殊變量,能夠被直接引用,可是有特殊用途,好比上面的 __name__ 就是特殊變量,還有 __author__ 也是特殊變量,用來標明做者。注意,咱們本身的變量通常不要用這種變量名;相似_xxx__xxx 這樣的函數或變量就是非公開的(private),不該該被直接引用,好比 _abc__abc 等.

注意:這裏是說不該該,而不是不能。由於 Python 種並無一種方法能夠徹底限制訪問 private 函數或變量,可是,從編程習慣上不該該引用 private 函數或變量。

3、面向對象

Python 對屬性的訪問控制是靠程序員自覺的。

咱們也能夠把方法當作是類的屬性的,那麼方法的訪問控制也是跟屬性是同樣的,也是沒有實質上的私有方法。一切都是靠程序員自覺遵照 Python 的編程規範。

3.1 類

3.1.1 方法的裝飾器

  • @classmethod:調用的時候直接使用類名類調用,而不是某個對象

  • @property:能夠像訪問屬性同樣調用方法

class UserInfo:

    ...

 @classmethod
    def get_name(cls):
        return cls.lv

 @property
    def get_age(self):
        return self._age

    if __name__ == '__main__':   
        ...

        # 直接使用類名類調用,而不是某個對象
        print(UserInfo.lv)
        # 像訪問屬性同樣調用方法(注意看get_age是沒有括號的)
        print(userInfo.get_age)
複製代碼

3.1.2 繼承

語法格式:

class ClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>
複製代碼

固然上面的是單繼承,Python 也是支持多繼承的(注意: Java 是單繼承、多實現),具體的語法以下:

class ClassName(Base1,Base2,Base3):
    <statement-1>
    .
    .
    .
    <statement-N>
複製代碼

多繼承有一點須要注意的:如果父類中有相同的方法名,而在子類使用時未指定,Python 在圓括號中父類的順序,從左至右搜索 , 即方法在子類中未找到時,從左到右查找父類中是否包含方法。

繼承的子類的好處:

  • 會繼承父類的屬性和方法
  • 能夠本身定義,覆蓋父類的屬性和方法

3.1.3 多態

看個例子就行了:

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

    def printUser(self):
        print('Hello !' + self.name)

class UserVip(User):
    def printUser(self):
        print('Hello ! 尊敬的Vip用戶:' + self.name)

class UserGeneral(User):
    def printUser(self):
        print('Hello ! 尊敬的用戶:' + self.name)

def printUserInfo(user):
    user.printUser()

if __name__ == '__main__':
    userVip = UserVip('大金主')
    printUserInfo(userVip)
    userGeneral = UserGeneral('水貨')
    printUserInfo(userGeneral)
複製代碼

輸出結果:

Hello ! 尊敬的Vip用戶:大金主
Hello ! 尊敬的用戶:水貨
複製代碼

能夠看到,userVip 和 userGeneral 是兩個不一樣的對象,對它們調用 printUserInfo 方法,它們會自動調用實際類型的 printUser 方法,做出不一樣的響應。這就是多態的魅力。

PS:有了繼承,纔有了多態,也會有不一樣類的對象對同一消息會做出不一樣的相應。

3.1.4 Python中的魔法方法

在 Python 中,全部以 "**" 雙下劃線包起來的方法,都統稱爲"魔術方法"。好比咱們接觸最多的 init__ 。魔術方法有什麼做用呢?

使用這些魔術方法,咱們能夠構造出優美的代碼,將複雜的邏輯封裝成簡單的方法。

咱們可使用 Python 內置的方法 dir() 來列出類中全部的魔術方法。示例以下:

class User(object):
    pass


if __name__ == '__main__':
    print(dir(User()))
複製代碼

輸出的結果:

能夠看到,一個類的魔術方法仍是挺多的,截圖沒有截全。不過咱們只須要了解一些常見和經常使用的魔術方法就行了。

一、屬性的訪問控制

Python 沒有真正意義上的私有屬性。而後這就致使了對 Python 類的封裝性比較差。咱們有時候會但願 Python 可以定義私有屬性,而後提供公共可訪問的 get 方法和 set 方法。Python 其實能夠經過魔術方法來實現封裝。

方法 說明
__getattr__(self, name) 該方法定義了你試圖訪問一個不存在的屬性時的行爲。所以,重載該方法能夠實現捕獲錯誤拼寫而後進行重定向,或者對一些廢棄的屬性進行警告。
__setattr__(self, name, value) 定義了對屬性進行賦值和修改操做時的行爲。無論對象的某個屬性是否存在,都容許爲該屬性進行賦值。有一點須要注意,實現 __setattr__ 時要避免"無限遞歸"的錯誤
__delattr__(self, name) __delattr____setattr__ 很像,只是它定義的是你刪除屬性時的行爲。實現 __delattr__ 是同時要避免"無限遞歸"的錯誤
__getattribute__(self, name) __getattribute__ 定義了你的屬性被訪問時的行爲,相比較,__getattr__ 只有該屬性不存在時纔會起做用。所以,在支持 __getattribute__的 Python 版本,調用__getattr__ 前一定會調用 __getattribute____getattribute__ 一樣要避免"無限遞歸"的錯誤。

二、對象的描述器

通常來講,一個描述器是一個有「綁定行爲」的對象屬性 (object attribute),它的訪問控制被描述器協議方法重寫。這些方法是 __get__()__set__()__delete__()。有這些方法的對象叫作描述器。

默認對屬性的訪問控制是從對象的字典裏面 (__dict__) 中獲取 (get) , 設置 (set) 和刪除 (delete) 。舉例來講, a.x 的查找順序是 a.__dict__['x'],而後 type(a).__dict__['x'],而後找 type(a) 的父類 ( 不包括元類 (metaclass) )。若是查找到的值是一個描述器,Python 就會調用描述器的方法來重寫默認的控制行爲。這個重寫發生在這個查找環節的哪裏取決於定義了哪一個描述器方法。注意,只有在新式類中時描述器纔會起做用。

至於新式類最大的特色就是全部類都繼承自 type 或者 object 的類。

在面向對象編程時,若是一個類的屬性有相互依賴的關係時,使用描述器來編寫代碼能夠很巧妙的組織邏輯。在 Django 的 ORM 中,models.Model 中的 InterField 等字段,就是經過描述器來實現功能的。

看一個例子:

class User(object):
    def __init__(self, name='小明', sex='男'):
        self.sex = sex
        self.name = name

    def __get__(self, obj, objtype):
        print('獲取 name 值')
        return self.name

    def __set__(self, obj, val):
        print('設置 name 值')
        self.name = val

class MyClass(object):
    x = User('小明', '男')
    y = 5

if __name__ == '__main__':
    m = MyClass()
    print(m.x)

    print('\n')

    m.x = '大明'
    print(m.x)

    print('\n')

    print(m.x)

    print('\n')

    print(m.y)
複製代碼

輸出結果:

獲取 name 值
小明


設置 name 值
獲取 name 值
大明


獲取 name 值
大明


5
複製代碼

三、自定義容器(Container)

咱們知道在 Python 中,常見的容器類型有:dict、tuple、list、string。其中也提到過可容器和不可變容器的概念。其中 tuple、string 是不可變容器,dict、list 是可變容器。

可變容器和不可變容器的區別在於,不可變容器一旦賦值後,不可對其中的某個元素進行修改。

那麼這裏先提出一個問題,這些數據結構就夠咱們開發使用嗎?不夠的時候,或者說有些特殊的需求不能單單隻使用這些基本的容器解決的時候,該怎麼辦呢?

這個時候就須要自定義容器了,那麼具體咱們該怎麼作呢?

功能 說明
自定義不可變容器類型 須要定義 __len____getitem__方法
自定義可變類型容器 在不可變容器類型的基礎上增長定義 __setitem____delitem__
自定義的數據類型須要迭代 需定義 __iter__
返回自定義容器的長度 需實現 __len__(self)
自定義容器能夠調用 self[key],若是 key 類型錯誤,拋出 TypeError,若是無法返回 key對應的數值時,該方法應該拋出 ValueError 須要實現 __getitem__(self, key)
當執行 self[key] = value 調用是 __setitem__(self, key, value)這個方法
當執行 del self[key] 方法 其實調用的方法是 __delitem__(self, key)
當你想你的容器能夠執行 for x in container: 或者使用 iter(container) 須要實現 __iter__(self) ,該方法返回的是一個迭代器

還有不少魔術方法,好比運算符相關的模式方法,就不在該文展開了。

3.2 枚舉類

3.2.1 什麼是枚舉

舉例,直接看代碼:

from enum import Enum

Month = Enum('Month1', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 遍歷枚舉類型
for name, member in Month.__members__.items():
    print(name, '---------', member, '----------', member.value)

# 直接引用一個常量
print('\n', Month.Jan)
複製代碼

輸出結果:

Jan --------- Month1.Jan ---------- 1
Feb --------- Month1.Feb ---------- 2
Mar --------- Month1.Mar ---------- 3
Apr --------- Month1.Apr ---------- 4
May --------- Month1.May ---------- 5
Jun --------- Month1.Jun ---------- 6
Jul --------- Month1.Jul ---------- 7
Aug --------- Month1.Aug ---------- 8
Sep --------- Month1.Sep ---------- 9
Oct --------- Month1.Oct ---------- 10
Nov --------- Month1.Nov ---------- 11
Dec --------- Month1.Dec ---------- 12

Month.Jan
複製代碼

可見,咱們能夠直接使用 Enum 來定義一個枚舉類。上面的代碼,咱們建立了一個有關月份的枚舉類型 Month,這裏要注意的是構造參數,第一個參數 Month 表示的是該枚舉類的類名,第二個 tuple 參數,表示的是枚舉類的值; 固然,枚舉類經過 __members__ 遍歷它的全部成員的方法。

注意的一點是 , member.value 是自動賦給成員的 int 類型的常量,默認是從 1 開始的。並且 Enum 的成員均爲單例(Singleton),而且不可實例化,不可更改。

3.2.2 自定義枚舉類型

有時候咱們須要控制枚舉的類型,那麼咱們能夠 Enum 派生出自定義類來知足這種須要。修改上面的例子:

from enum import Enum, unique

Enum('Month1', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# @unique 裝飾器能夠幫助咱們檢查保證沒有重複值
@unique
class Month1(Enum):
    Jan = 'January'
    Feb = 'February'
    Mar = 'March'
    Apr = 'April'
    May = 'May'
    Jun = 'June'
    Jul = 'July'
    Aug = 'August'
    Sep = 'September '
    Oct = 'October'
    Nov = 'November'
    Dec = 'December'

if __name__ == '__main__':
    print(Month1.Jan, '----------',
          Month1.Jan.name, '----------', Month1.Jan.value)
    for name, member in Month1.__members__.items():
        print(name, '----------', member, '----------', member.value)
複製代碼

輸出結果:

Month1.Jan ---------- Jan ---------- January
Jan ---------- Month1.Jan ---------- January
Feb ---------- Month1.Feb ---------- February
Mar ---------- Month1.Mar ---------- March
Apr ---------- Month1.Apr ---------- April
May ---------- Month1.May ---------- May
Jun ---------- Month1.Jun ---------- June
Jul ---------- Month1.Jul ---------- July
Aug ---------- Month1.Aug ---------- August
Sep ---------- Month1.Sep ---------- September 
Oct ---------- Month1.Oct ---------- October
Nov ---------- Month1.Nov ---------- November
Dec ---------- Month1.Dec ---------- December
複製代碼

4.2.3 枚舉類的比較

由於枚舉成員不是有序的,因此它們只支持經過標識(identity) 和相等性 (equality) 進行比較。下面來看看 ==is 的使用:

from enum import Enum

class User(Enum):
    Twowater = 98
    Liangdianshui = 30
    Tom = 12

Twowater = User.Twowater
Liangdianshui = User.Liangdianshui

print(Twowater == Liangdianshui, Twowater == User.Twowater)
print(Twowater is Liangdianshui, Twowater is User.Twowater)

try:
    print('\n'.join(' ' + s.name for s in sorted(User)))
except TypeError as err:
    print(' Error : {}'.format(err))
複製代碼

輸出結果:

False True
False True
 Error : '<' not supported between instances of 'User' and 'User' 複製代碼

能夠看看最後的輸出結果,報了個異常,那是由於大於和小於比較運算符引起 TypeError 異常。也就是 Enum 類的枚舉是不支持大小運算符的比較的。

可是使用 IntEnum 類進行枚舉,就支持比較功能。

import enum

class User(enum.IntEnum):
    Twowater = 98
    Liangdianshui = 30
    Tom = 12
    
try:
    print('\n'.join(s.name for s in sorted(User)))
except TypeError as err:
    print(' Error : {}'.format(err))
複製代碼

輸出結果:

Tom
Liangdianshui
Twowater
複製代碼

經過輸出的結果能夠看到,枚舉類的成員經過其值得大小進行了排序。也就是說能夠進行大小的比較。

3.3 元類

3.3.1 Python 中類也是對象

在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在 Python 中這一點也是同樣的。可是,Python 中的類有一點跟大多數的編程語言不一樣,在 Python 中,能夠把類理解成也是一種對象。對的,這裏沒有寫錯,就是對象。

由於只要使用關鍵字 class,Python 解釋器在執行的時候就會建立一個對象。如:

class ObjectCreator(object):
    pass
複製代碼

當程序運行這段代碼的時候,就會在內存中建立一個對象,名字就是ObjectCreator。這個對象(類)自身擁有建立對象(類實例)的能力,而這就是爲何它是一個類的緣由。

3.3.2 使用type()動態建立類

由於類也是對象,因此咱們能夠在程序運行的時候建立類。Python 是動態語言。動態語言和靜態語言最大的不一樣,就是函數和類的定義,不是編譯時定義的,而是運行時動態建立的。在以前,咱們先了瞭解下 type() 函數。

class Hello(object):
    def hello(self, name='Py'):
        print('Hello,', name)
複製代碼

而後再另一個模塊引用 hello 模塊,輸出相應信息。(其中 type() 函數的做用是能夠查看一個類型和變量的類型。)

from com.strivebo.hello import Hello

h = Hello()
h.hello()

print(type(Hello))
print(type(h))
複製代碼

輸出信息:

Hello, Py
<class 'type'>
<class 'com.twowater.hello.Hello'>
複製代碼

上面也提到過,type() 函數能夠查看一個類型或變量的類型,Hello 是一個 class ,它的類型就是 type ,而 h 是一個實例,它的類型就是 com.strivebo.hello.Hello。前面的 com.strivebo 是個人包名,hello 模塊在該包名下。

在這裏還要細想一下,上面的例子中,咱們使用 type() 函數查看一個類型或者變量的類型。其中查看了一個 Hello class 的類型,打印的結果是: <class 'type'>。其實 type() 函數不只能夠返回一個對象的類型,也能夠建立出新的類型。class 的定義是運行時動態建立的,而建立 class 的方法就是使用 type() 函數。好比咱們能夠經過 type() 函數建立出上面例子中的 Hello 類,具體看下面的代碼:

def printHello(self, name='Py'):
    # 定義一個打印 Hello 的函數
    print('Hello,', name)

# 建立一個 Hello 類
Hello = type('Hello', (object,), dict(hello=printHello))

# 實例化 Hello 類
h = Hello()
# 調用 Hello 類的方法
h.hello()
# 查看 Hello class 的類型
print(type(Hello))
# 查看實例 h 的類型
print(type(h))
複製代碼

輸出結果:

Hello, Py
<class 'type'>
<class '__main__.Hello'>
複製代碼

在這裏,需先了解下經過 type() 函數建立 class 對象的參數說明:

  1. class 的名稱,好比例子中的起名爲 Hello
  2. 繼承的父類集合,注意 Python 支持多重繼承,若是隻有一個父類,tuple 要使用單元素寫法;例子中繼承 object 類,由於是單元素的 tuple ,因此寫成 (object,)
  3. class 的方法名稱與函數綁定;例子中將函數 printHello 綁定在方法名 hello

具體的模式以下:type(類名, 父類的元組(針對繼承的狀況,能夠爲空),包含屬性的字典(名稱和值))

好了,瞭解完具體的參數使用以外,咱們看看輸出的結果,能夠看到,經過 type() 函數建立的類和直接寫 class 是徹底同樣的,由於 Python 解釋器遇到 class 定義時,僅僅是掃描一下 class 定義的語法,而後調用 type() 函數建立出 class 的。

3.3.3 什麼是元類

咱們建立類的時候,大多數是爲了建立類的實例對象。那麼元類呢?元類就是用來建立類的。也能夠換個理解方式就是:元類就是類的類。

經過上面 type() 函數的介紹,咱們知道能夠經過 type() 函數建立類:MyClass = type('MyClass', (), {})

實際上 type() 函數是一個元類。type() 就是 Python 在背後用來建立全部類的元類。

那麼如今咱們也能夠猜到一下爲何 type() 函數是 type 而不是 Type呢?

這多是爲了和 str 保持一致性,str 是用來建立字符串對象的類,而 int 是用來建立整數對象的類。type 就是建立類對象的類。

能夠看到,上面的全部東西,也就是全部對象都是經過類來建立的,那麼咱們可能會好奇,__class____class__ 會是什麼呢?換個說法就是,建立這些類的類是什麼呢?

print(age.__class__.__class__)
print(name.__class__.__class__)
print(fu.__class__.__class__)
print(mEat.__class__.__class__)
複製代碼

輸出結果:

<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
複製代碼

能夠看出,把他們類的類打印結果。發現打印出來的 class 都是 type 。

一開始也提到了,元類就是類的類。也就是元類就是負責建立類的一種東西。你也能夠理解爲,元類就是負責生成類的。而 type 就是內建的元類。也就是 Python 自帶的元類。

3.3.4 自定義元類

鏈接起來就是:先定義 metaclass,就能夠建立類,最後建立實例。

因此,metaclass 容許你建立類或者修改類。換句話說,你能夠把類當作是 metaclass 建立出來的「實例」。

class MyObject(object):
    __metaclass__ = something…
[…]
複製代碼

若是是這樣寫的話,Python 就會用元類來建立類 MyObject。當你寫下 class MyObject(object),可是類對象 MyObject 尚未在內存中建立。Python 會在類的定義中尋找 __metaclass__ 屬性,若是找到了,Python 就會用它來建立類 MyObject,若是沒有找到,就會用內建的 type 函數來建立這個類。若是還不怎麼理解,看下下面的流程圖:

舉個實例:

class Foo(Bar):
    pass
複製代碼

它的流程是怎樣的呢?

首先判斷 Foo 中是否有 __metaclass__ 這個屬性?若是有,Python 會在內存中經過 __metaclass__ 建立一個名字爲 Foo 的類對象(注意,這裏是類對象)。若是 Python 沒有找到__metaclass__ ,它會繼續在 Bar(父類)中尋找__metaclass__ 屬性,並嘗試作和前面一樣的操做。若是 Python在任何父類中都找不到 __metaclass__,它就會在模塊層次中去尋找__metaclass__ ,並嘗試作一樣的操做。若是仍是找不到__metaclass__ ,Python 就會用內置的 type 來建立這個類對象。

其實 __metaclass__ 就是定義了 class 的行爲。相似於 class 定義了 instance 的行爲,metaclass 則定義了 class 的行爲。能夠說,class 是 metaclass 的 instance。

如今,咱們基本瞭解了 __metaclass__ 屬性,可是,也沒講過如何使用這個屬性,或者說這個屬性能夠放些什麼?

答案就是:能夠建立一個類的東西。那麼什麼能夠用來建立一個類呢?type,或者任何使用到 type 或者子類化 type 的東東均可以。

3.4.5 元類的做用

元類的主要目的就是爲了當建立類時可以自動地改變類。一般,你會爲 API 作這樣的事情,你但願能夠建立符合當前上下文的類。

假想一個很傻的例子,你決定在你的模塊裏全部的類的屬性都應該是大寫形式。有好幾種方法能夠辦到,但其中一種就是經過在模塊級別設定__metaclass__ 。採用這種方法,這個模塊中的全部類都會經過這個元類來建立,咱們只須要告訴元類把全部的屬性都改爲大寫形式就萬事大吉了。

總結:Python 中的一切都是對象,它們要麼是類的實例,要麼是元類的實例,除了 type。type 其實是它本身的元類,在純 Python 環境中這可不是你可以作到的,這是經過在實現層面耍一些小手段作到的。

4、線程與進程

線程和進程的概念我就很少贅述了。可自行網上搜索查找資料瞭解下。

直接看問題:在 Python 中咱們要同時執行多個任務怎麼辦?

有兩種解決方案:

  1. 一種是啓動多個進程,每一個進程雖然只有一個線程,但多個進程能夠一塊執行多個任務。
  2. 還有一種方法是啓動一個進程,在一個進程內啓動多個線程,這樣,多個線程也能夠一塊執行多個任務。

固然還有第三種方法,就是啓動多個進程,每一個進程再啓動多個線程,這樣同時執行的任務就更多了,固然這種模型更復雜,實際不多采用。

總結一下就是,多任務的實現有3種方式:

  • 多進程模式;
  • 多線程模式;
  • 多進程+多線程模式。

同時執行多個任務一般各個任務之間並非沒有關聯的,而是須要相互通訊和協調,有時,任務 1 必須暫停等待任務 2 完成後才能繼續執行,有時,任務 3 和任務 4 又不能同時執行,因此,多進程和多線程的程序的複雜度要遠遠高於咱們前面寫的單進程單線程的程序。

4.1 多線程編程

其實建立線程以後,線程並非始終保持一個狀態的,其狀態大概以下:

  • New 建立
  • Runnable 就緒。等待調度
  • Running 運行
  • Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
  • Dead 消亡

線程有着不一樣的狀態,也有不一樣的類型。大體可分爲:

  • 主線程
  • 子線程
  • 守護線程(後臺線程)
  • 前臺線程

線程的建立:

Python 提供兩個模塊進行多線程的操做,分別是 threadthreading

前者是比較低級的模塊,用於更底層的操做,通常應用級別的開發不經常使用。

import time
import threading


class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            print('thread {}, @number: {}'.format(self.name, i))
            time.sleep(1)

def main():
    print("Start main threading")

    # 建立三個線程
    threads = [MyThread() for i in range(3)]
    # 啓動三個線程
    for t in threads:
        t.start()

    print("End Main threading")


if __name__ == '__main__':
    main()
複製代碼

這塊的內容還有不少,因爲該文重點仍是爲講解 Python 的基礎知識。線程和進程的內容更多仍是到網上搜索資料學習,亦或是往後有時間我再更新於此。

5、Python 正則表達式

正則表達式是一個特殊的字符序列,用於判斷一個字符串是否與咱們所設定的字符序列是否匹配,也就是說檢查一個字符串是否與某種模式匹配。

Python 自 1.5 版本起增長了 re 模塊,它提供 Perl 風格的正則表達式模式。re 模塊使 Python 語言擁有所有的正則表達式功能。

以下代碼:

# 設定一個常量
a = '學習Python不難'

# 判斷是否有 「Python」 這個字符串,使用 PY 自帶函數

print('是否含有「Python」這個字符串:{0}'.format(a.index('Python') > -1))
print('是否含有「Python」這個字符串:{0}'.format('Python' in a))
複製代碼

輸出結果:

是否含有「Python」這個字符串:True
是否含有「Python」這個字符串:True
複製代碼

上面用 Python 自帶函數就能解決的問題,咱們就不必使用正則表達式了,這樣作畫蛇添足。

直接舉個 Python 中正則表達式使用例子好了:找出字符串中的全部小寫字母。

首先咱們在 findall 函數中第一個參數寫正則表達式的規則,其中[a-z]就是匹配任何小寫字母,第二個參數只要填寫要匹配的字符串就好了。具體以下:

import re

# 設定一個常量
a = '學習Python不難'

# 選擇 a 裏面的全部小寫英文字母

re_findall = re.findall('[a-z]', a)

print(re_findall)
複製代碼

輸出結果:

['y', 't', 'h', 'o', 'n']
複製代碼

這樣咱們就拿到了字符串中的全部小寫字母了。

補充:

  • 貪婪模式:它的特性是一次性地讀入整個字符串,若是不匹配就吐掉最右邊的一個字符再匹配,直到找到匹配的字符串或字符串的長度爲 0 爲止。它的宗旨是讀儘量多的字符,因此當讀到第一個匹配時就馬上返回。
  • 懶惰模式:它的特性是從字符串的左邊開始,試圖不讀入字符串中的字符進行匹配,失敗,則多讀一個字符,再匹配,如此循環,當找到一個匹配時會返回該匹配的字符串,而後再次進行匹配直到字符串結束。

關於正則表達式的更多的學習仍是找網上資料看看吧。

6、閉包

經過解決一個需求問題來了解閉包。

這個需求是這樣的,咱們須要一直記錄本身的學習時間,以分鐘爲單位。就比如我學習了 2 分鐘,就返回 2 ,而後隔了一陣子,我學習了 10 分鐘,那麼就返回 12 ,像這樣把學習時間一直累加下去。

面對這個需求,咱們通常都會建立一個全局變量來記錄時間,而後用一個方法來新增每次的學習時間,一般都會寫成下面這個形式:

time = 0

def insert_time(min):
    time = time + min
    return  time

print(insert_time(2))
print(insert_time(10))
複製代碼

其實,這個在 Python 裏面是會報錯的。會報以下錯誤:UnboundLocalError: local variable 'time' referenced before assignment

那是由於,在 Python 中,若是一個函數使用了和全局變量相同的名字且改變了該變量的值,那麼該變量就會變成局部變量,那麼就會形成在函數中咱們沒有進行定義就引用了,因此會報該錯誤。

咱們可使用 global 關鍵字,具體修改以下:

time = 0

def insert_time(min):
    global  time
    time = time + min
    return  time

print(insert_time(2))
print(insert_time(10))
複製代碼

輸出結果以下:

2
12
複製代碼

但是啊,這裏使用了全局變量,咱們在開發中能儘可能避免使用全局變量的就儘可能避免使用。由於不一樣模塊,不一樣函數均可以自由的訪問全局變量,可能會形成全局變量的不可預知性。好比程序員甲修改了全局變量 time 的值,而後程序員乙同時也對 time 進行了修改,若是其中有錯誤,這種錯誤是很難發現和更正的。

這時候咱們使用閉包來解決一下,先直接看代碼:

time = 0

def study_time(time):
    def insert_time(min):
        nonlocal  time
        time = time + min
        return time

    return insert_time

f = study_time(time)
print(f(2))
print(time)
print(f(10))
print(time)
複製代碼

輸出結果以下:

2
0
12
0
複製代碼

這裏最直接的表現就是全局變量 time 至此至終都沒有修改過,這裏仍是用了 nonlocal 關鍵字,表示在函數或其餘做用域中使用外層(非全局)變量。那麼上面那段代碼具體的運行流程是怎樣的。咱們能夠看下下圖:

這種內部函數的局部做用域中能夠訪問外部函數局部做用域中變量的行爲,咱們稱爲: 閉包。 更加直接的表達方式就是,當某個函數被當成對象返回時,夾帶了外部變量,就造成了一個閉包。

有沒有什麼辦法來驗證一下這個函數就是閉包呢?

有的,全部函數都有一個 __closure__ 屬性,若是函數是閉包的話,那麼它返回的是一個由 cell 組成的元組對象。cell 對象的 cell_contents 屬性就是存儲在閉包中的變量。看代碼:

ime = 0


def study_time(time):
    def insert_time(min):
        nonlocal  time
        time = time + min
        return time

    return insert_time


f = study_time(time)
print(f.__closure__)
print(f(2))
print(time)
print(f.__closure__[0].cell_contents)
print(f(10))
print(time)
print(f.__closure__[0].cell_contents)
複製代碼

打印結果爲:

(<cell at 0x0000000000410C48: int object at 0x000000001D6AB420>,)
2
0
2
12
0
12
複製代碼

從打印結果可見,傳進來的值一直存儲在閉包的 cell_contents 中,所以,這也就是閉包的最大特色,能夠將父函數的變量與其內部定義的函數綁定。就算生成閉包的父函數已經釋放了,閉包仍然存在。

閉包的過程其實比如類(父函數)生成實例(閉包),不一樣的是父函數只在調用時執行,執行完畢後其環境就會釋放,而類則在文件執行時建立,通常程序執行完畢後做用域才釋放,所以對一些須要重用的功能且不足以定義爲類的行爲,使用閉包會比使用類佔用更少的資源,且更輕巧靈活。

7、裝飾器

7.1 什麼是裝飾器

經過一個需求,一步一步來了解 Python 裝飾器。首先有這麼一個輸出員工打卡信息的函數:

def punch():
    print('暱稱:小明 部門:研發部 上班打卡成功')

punch()
複製代碼

輸出的結果:

暱稱:小明  部門:研發部 上班打卡成功
複製代碼

而後,產品反饋,不行啊,怎麼上班打卡沒有具體的日期,加上打卡的具體日期吧,這應該很簡單,分分鐘解決啦。好吧,那就直接添加打印日期的代碼吧,以下:

import time

def punch():
    print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
    print('暱稱:小明 部門:研發部 上班打卡成功')

punch()
複製代碼

輸出的結果:

2018-01-09
暱稱:小明  部門:研發部 上班打卡成功
複製代碼

這樣改是能夠,但是這樣改是改變了函數的功能結構的,自己這個函數定義的時候就是打印某個員工的信息和提示打卡成功,如今增長打印日期的代碼,可能會形成不少代碼重複的問題。好比,還有一個地方只須要打印員工信息和打卡成功就好了,不須要日期,那麼你又要重寫一個函數嗎?並且打印當前日期的這個功能方法是常用的,是能夠做爲公共函數給各個模塊方法調用的。固然,這都是做爲一個總體項目來考慮的。

既然是這樣,咱們可使用函數式編程來修改這部分的代碼。由於經過以前的學習,咱們知道 Python 函數有兩個特色,函數也是一個對象,並且函數裏能夠嵌套函數,那麼修改一下代碼變成下面這個樣子:

import time

def punch():
    print('暱稱:小明 部門:研發部 上班打卡成功')

def add_time(func):
    print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
    func()

add_time(punch)
複製代碼

輸出的結果:

2018-01-09
暱稱:小明  部門:研發部 上班打卡成功
複製代碼

這樣是否是發現,這樣子就沒有改動 punch 方法,並且任何須要用到打印當前日期的函數均可以把函數傳進 add_time 就能夠了。

使用函數編程是否是很方便,可是,咱們每次調用的時候,咱們都不得不把原來的函數做爲參數傳遞進去,還能不能有更好的實現方式呢?有的,就是本文要介紹的裝飾器,由於裝飾器的寫法其實跟閉包是差很少的,不過沒有了自由變量,那麼這裏直接給出上面那段代碼的裝飾器寫法,來對比一下,裝飾器的寫法和函數式編程有啥不一樣。

import time

def decorator(func):
    def punch():
        print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
        func()

    return punch

def punch():
    print('暱稱:小明 部門:研發部 上班打卡成功')

f = decorator(punch)
f()
複製代碼

輸出的結果:

2018-01-09
暱稱:小明  部門:研發部 上班打卡成功
複製代碼

經過代碼,能知道裝飾器函數通常作這三件事:

  1. 接收一個函數做爲參數
  2. 嵌套一個包裝函數, 包裝函數會接收原函數的相同參數,並執行原函數,且還會執行附加功能
  3. 返回嵌套函數

7.2 語法糖

咱們看上面的代碼能夠知道, Python 在引入裝飾器 (Decorator) 的時候,沒有引入任何新的語法特性,都是基於函數的語法特性。這也就說明了裝飾器不是 Python 特有的,而是每一個語言通用的一種編程思想。只不過 Python 設計出了 @ 語法糖,讓定義裝飾器,把裝飾器調用原函數再把結果賦值爲原函數的對象名的過程變得更加簡單,方便,易操做,因此 Python 裝飾器的核心能夠說就是它的語法糖。

那麼怎麼使用它的語法糖呢?很簡單,根據上面的寫法寫完裝飾器函數後,直接在原來的函數上加 @ 和裝飾器的函數名。以下:

import time

def decorator(func):
    def punch():
        print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
        func()

    return punch

@decorator
def punch():
    print('暱稱:小明 部門:研發部 上班打卡成功')

punch()
複製代碼

輸出結果:

2018-01-09
暱稱:小明  部門:研發部 上班打卡成功
複製代碼

那麼這就很方便了,方便在咱們的調用上,好比例子中的,使用了裝飾器後,直接在本來的函數上加上裝飾器的語法糖就能夠了,本函數也無虛任何改變,調用的地方也不需修改。

不過這裏一直有個問題,就是輸出打卡信息的是固定的,那麼咱們須要經過參數來傳遞,裝飾器該怎麼寫呢?裝飾器中的函數可使用 *args 可變參數,但是僅僅使用 *args 是不能徹底包括全部參數的狀況,好比關鍵字參數就不能了,爲了能兼容關鍵字參數,咱們還須要加上 **kwargs

所以,裝飾器的最終形式能夠寫成這樣:

import time

def decorator(func):
    def punch(*args, **kwargs):
        print(time.strftime('%Y-%m-%d', time.localtime(time.time())))
        func(*args, **kwargs)

    return punch

 
@decorator
def punch(name, department):
    print('暱稱:{0} 部門:{1} 上班打卡成功'.format(name, department))

@decorator
def print_args(reason, **kwargs):
    print(reason)
    print(kwargs)

punch('小明', '研發部')
print_args('小明', sex='男', age=99)
複製代碼

輸出的結果:

2018-01-09
暱稱:小明  部門:研發部 上班打卡成功
2018-01-09
小明
{'sex': '男', 'age': 99}
複製代碼

8、自定義異常和主動拋出異常raise

有時候python自帶異常不夠用,如同java,python也能夠自定義異常,而且能夠手動拋出。注意,自定義異常只能由本身拋出。python解釋器是不知道用戶自定義異常是什麼鬼的。

raise語句:

主動拋出異常。 格式:

  • 主動拋出異常終止程序
  • raise 異常名稱(‘異常描述’)
raise RuntimeError('testError')
複製代碼

主動拋出這個異常,並加以解釋。

自定義異常:

python的異常分爲兩種.

  1. 內建異常,就是python本身定義的異常。
  2. 不夠用,用戶自定義異常

首先看看 python 的異常繼承樹:

咱們能夠看到 python 的異常有個大基類。而後繼承的是 Exception。因此咱們自定義類也必須繼承 Exception。

#最簡單的自定義異常
class FError(Exception):
    pass
複製代碼

拋出異常、用try-except拋出:

try:
    raise FError("自定義異常")
except FError as e:
    print(e)
複製代碼

在這裏給一個簡單的自定義異常類模版。

class CustomError(Exception):
    def __init__(self,ErrorInfo):
        super().__init__(self) #初始化父類
        self.errorinfo=ErrorInfo
    def __str__(self):
        return self.errorinfo

if __name__ == '__main__':
    try:
        raise CustomError('客戶異常')
    except CustomError as e:
        print(e)
複製代碼

——from:blog.csdn.net/skullFang/a…

本文內容大部分來源:

相關文章
相關標籤/搜索