日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

Python基础入门笔记(二)

發布時間:2023/12/20 python 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python基础入门笔记(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

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

一、迭代器和生成器

1.1 Python迭代器

迭代器是一個可以記住遍歷的位置的對象。

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

迭代器只能往前不會后退。

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

具體的實例:

# 1、字符創創建迭代器對象 str1 = 'jaybo' iter1 = iter ( str1 )# 2、list對象創建迭代器 list1 = [1,2,3,4] iter2 = iter ( list1 )# 3、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 LL.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 + 1if 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.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 sysdef shout():args = sys.argvif 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 sysprint(sys.path)

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

①導入模塊的方法

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

PS1:導入模塊并不意味著在導入時執行某些操作,它們主要用于定義,比如變量、函數和類等。

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

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

  • module.variable
  • module.function()
  • 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 中去,這樣在任何位置都可以調用包了(推薦)。

    ②包中模塊的導入

  • import 包名.模塊名
  • import 包名.模塊名 as 新名字
  • from 包名 import 模塊名
  • ③包中模塊的變量、函數以及類的屬性和方法的調用

  • package.module.variable
  • package.module.function()
  • package.module.class.variable
  • 2.3 作用域

    學習過 Java 的同學都知道,Java 的類里面可以給方法和屬性定義公共的( public )或者是私有的 ( private ),這樣做主要是為了我們希望有些函數和屬性能給別人使用或者只能內部使用。 通過學習 Python 中的模塊,其實和 Java 中的類相似,那么我們怎么實現在一個模塊中,有的函數和變量給別人使用,有的函數和變量僅僅在模塊內部使用呢?

    在 Python 中,是通過 _ 前綴來實現的。正常的函數和變量名是公開的(public),可以被直接引用,比如:abc,ni12,PI 等。

    類似__xxx__這樣的變量是特殊變量,可以被直接引用,但是有特殊用途,比如上面的 __name__ 就是特殊變量,還有 __author__ 也是特殊變量,用來標明作者。注意,我們自己的變量一般不要用這種變量名;類似_xxx 和 __xxx 這樣的函數或變量就是非公開的(private),不應該被直接引用,比如 _abc ,__abc 等.

    注意:這里是說不應該,而不是不能。因為 Python 種并沒有一種方法可以完全限制訪問 private 函數或變量,但是,從編程習慣上不應該引用 private 函數或變量。

    三、面向對象

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

    我們也可以把方法看成是類的屬性的,那么方法的訪問控制也是跟屬性是一樣的,也是沒有實質上的私有方法。一切都是靠程序員自覺遵守 Python 的編程規范。

    3.1 類

    3.1.1 方法的裝飾器

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

    • @property:可以像訪問屬性一樣調用方法

    class UserInfo:...@classmethoddef get_name(cls):return cls.lv@propertydef get_age(self):return self._ageif __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 = namedef 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):passif __name__ == '__main__':print(dir(User()))

    輸出的結果:

    可以看到,一個類的魔術方法還是挺多的,截圖沒有截全。不過我們只需要了解一些常見和常用的魔術方法就好了。

    1、屬性的訪問控制

    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__ 同樣要避免"無限遞歸"的錯誤。

    2、對象的描述器

    一般來說,一個描述器是一個有“綁定行為”的對象屬性 (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 = sexself.name = namedef __get__(self, obj, objtype):print('獲取 name 值')return self.namedef __set__(self, obj, val):print('設置 name 值')self.name = valclass MyClass(object):x = User('小明', '男')y = 5if __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

    3、自定義容器(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 EnumMonth = 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 ---------- 12Month.Jan

    可見,我們可以直接使用 Enum 來定義一個枚舉類。上面的代碼,我們創建了一個有關月份的枚舉類型 Month,這里要注意的是構造參數,第一個參數 Month 表示的是該枚舉類的類名,第二個 tuple 參數,表示的是枚舉類的值; 當然,枚舉類通過 __members__ 遍歷它的所有成員的方法。

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

    3.2.2 自定義枚舉類型

    有時候我們需要控制枚舉的類型,那么我們可以 Enum 派生出自定義類來滿足這種需要。修改上面的例子:

    from enum import Enum, uniqueEnum('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 Enumclass User(Enum):Twowater = 98Liangdianshui = 30Tom = 12Twowater = User.Twowater Liangdianshui = User.Liangdianshuiprint(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 TrueError : '<' not supported between instances of 'User' and 'User'

    可以看看最后的輸出結果,報了個異常,那是因為大于和小于比較運算符引發 TypeError 異常。也就是 Enum 類的枚舉是不支持大小運算符的比較的。

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

    import enumclass User(enum.IntEnum):Twowater = 98Liangdianshui = 30Tom = 12try: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 Helloh = 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 對象的參數說明:

  • class 的名稱,比如例子中的起名為 Hello
  • 繼承的父類集合,注意 Python 支持多重繼承,如果只有一個父類,tuple 要使用單元素寫法;例子中繼承 object 類,因為是單元素的 tuple ,所以寫成 (object,)
  • 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 環境中這可不是你能夠做到的,這是通過在實現層面耍一些小手段做到的。

    四、線程與進程

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

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

    有兩種解決方案:

  • 一種是啟動多個進程,每個進程雖然只有一個線程,但多個進程可以一塊執行多個任務。
  • 還有一種方法是啟動一個進程,在一個進程內啟動多個線程,這樣,多個線程也可以一塊執行多個任務。
  • 當然還有第三種方法,就是啟動多個進程,每個進程再啟動多個線程,這樣同時執行的任務就更多了,當然這種模型更復雜,實際很少采用。

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

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

    同時執行多個任務通常各個任務之間并不是沒有關聯的,而是需要相互通信和協調,有時,任務 1 必須暫停等待任務 2 完成后才能繼續執行,有時,任務 3 和任務 4 又不能同時執行,所以,多進程和多線程的程序的復雜度要遠遠高于我們前面寫的單進程單線程的程序。

    4.1 多線程編程

    其實創建線程之后,線程并不是始終保持一個狀態的,其狀態大概如下:

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

    線程有著不同的狀態,也有不同的類型。大致可分為:

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

    線程的創建:

    Python 提供兩個模塊進行多線程的操作,分別是 thread 和 threading

    前者是比較低級的模塊,用于更底層的操作,一般應用級別的開發不常用。

    import time import threadingclass 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 的基礎知識。線程和進程的內容更多還是到網上搜索資料學習,亦或是日后有時間我再更新于此。

    五、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 為止。它的宗旨是讀盡可能多的字符,所以當讀到第一個匹配時就立刻返回。
    • 懶惰模式:它的特性是從字符串的左邊開始,試圖不讀入字符串中的字符進行匹配,失敗,則多讀一個字符,再匹配,如此循環,當找到一個匹配時會返回該匹配的字符串,然后再次進行匹配直到字符串結束。

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

    六、閉包

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

    這個需求是這樣的,我們需要一直記錄自己的學習時間,以分鐘為單位。就好比我學習了 2 分鐘,就返回 2 ,然后隔了一陣子,我學習了 10 分鐘,那么就返回 12 ,像這樣把學習時間一直累加下去。

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

    time = 0def insert_time(min):time = time + minreturn timeprint(insert_time(2)) print(insert_time(10))

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

    那是因為,在 Python 中,如果一個函數使用了和全局變量相同的名字且改變了該變量的值,那么該變量就會變成局部變量,那么就會造成在函數中我們沒有進行定義就引用了,所以會報該錯誤。

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

    time = 0def insert_time(min):global timetime = time + minreturn timeprint(insert_time(2)) print(insert_time(10))

    輸出結果如下:

    2 12

    可是啊,這里使用了全局變量,我們在開發中能盡量避免使用全局變量的就盡量避免使用。因為不同模塊,不同函數都可以自由的訪問全局變量,可能會造成全局變量的不可預知性。比如程序員甲修改了全局變量 time 的值,然后程序員乙同時也對 time 進行了修改,如果其中有錯誤,這種錯誤是很難發現和更正的。

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

    time = 0def study_time(time):def insert_time(min):nonlocal timetime = time + minreturn timereturn insert_timef = 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 = 0def study_time(time):def insert_time(min):nonlocal timetime = time + minreturn timereturn insert_timef = 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.1 什么是裝飾器

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

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

    輸出的結果:

    昵稱:小明 部門:研發部 上班打卡成功

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

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

    輸出的結果:

    2018-01-09 昵稱:小明 部門:研發部 上班打卡成功

    這樣改是可以,可是這樣改是改變了函數的功能結構的,本身這個函數定義的時候就是打印某個員工的信息和提示打卡成功,現在增加打印日期的代碼,可能會造成很多代碼重復的問題。比如,還有一個地方只需要打印員工信息和打卡成功就行了,不需要日期,那么你又要重寫一個函數嗎?而且打印當前日期的這個功能方法是經常使用的,是可以作為公共函數給各個模塊方法調用的。當然,這都是作為一個整體項目來考慮的。

    既然是這樣,我們可以使用函數式編程來修改這部分的代碼。因為通過之前的學習,我們知道 Python 函數有兩個特點,函數也是一個對象,而且函數里可以嵌套函數,那么修改一下代碼變成下面這個樣子:

    import timedef 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 timedef decorator(func):def punch():print(time.strftime('%Y-%m-%d', time.localtime(time.time())))func()return punchdef punch():print('昵稱:小明 部門:研發部 上班打卡成功')f = decorator(punch) f()

    輸出的結果:

    2018-01-09 昵稱:小明 部門:研發部 上班打卡成功

    通過代碼,能知道裝飾器函數一般做這三件事:

  • 接收一個函數作為參數
  • 嵌套一個包裝函數, 包裝函數會接收原函數的相同參數,并執行原函數,且還會執行附加功能
  • 返回嵌套函數
  • 7.2 語法糖

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

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

    import timedef 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 timedef 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}

    本文內容大部分來源:

    • 兩點水:《草根學Python》

    總結

    以上是生活随笔為你收集整理的Python基础入门笔记(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。