python基礎(數據類型、函數、模塊與包、高級特性)

python簡介

python是什麼?python能做什麼?廖雪峯老師的python教程已經將的非常清楚。
python的安裝很簡單,此處也不再贅述。

查看python版本

# 查看python版本
python -V
# 進入到python解釋器查看
import sys
print(sys.version_info)
print(sys.version)

在這裏插入圖片描述

python註釋

單行註釋 - 以#和空格開頭的部分
多行註釋 - 三個引號開頭,三個引號結尾(引號可以是單引號也可以是雙引號,但必須成對出現)

"""
第一個Python程序 - hello, world!
多行註釋
"""
print('hello, world!')
# print("你好, 世界!")

python解釋器

整個Python語言從規範到解釋器都是開源的,我們需要Python解釋器來執行編寫的Python代碼(以.py結尾的文件)。存在多種Python解釋器,如CPython(官方提供的解釋器,平時我們安裝python其實就是安裝官方提供的CPython解釋器)、IPython(基於CPython的增強版交互式解釋器,但是內核還是CPython)、PyPyJythonIronPython。用的最廣泛的還是CPython,但是如果是交互式的話個人建議用IPythonIPython可以通過TAB鍵有提示的功能,交互性更強。

使用IPython

可以用python包管理工具pip安裝IPython
pip安裝(用哪個版本的 Python 運行安裝腳本,pip 就被關聯到哪個版本)

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py   # 下載安裝腳本
python get-pip.py    # 運行安裝腳本

執行pip install ipython進行安裝。安裝成功後執行ipython,如下圖所示。
如果安裝很慢可以參考vim筆記中關於pip配置部分配置pip的國內鏡像源。
在這裏插入圖片描述

python基礎

python變量類型

整形:任意大小的整數,如a=10
浮點型:小數,如a=10.6
字符串型: 單引號或者雙引號,如s="hello", p='world'。當某一行代碼太長,可以採用\的方式轉接到下一行。也可以用"""來定義多行字符串
布爾型: True/False,如flag=True
複數型: 3+5j

s = "壓縮,很多貌似無法壓縮的詞語或句子,被網友活生生地壓縮了,像高大上不明覺厲等詞。按照語文修辭學,這些詞是不能這樣壓縮的,但是網友就這麼壓縮了,而且傳播範圍很廣。\
反轉,本來是一種修辭格,在網絡時代,反轉修辭格的大量湧現有目共睹,如撩字,大家都非常熟悉,撩的基本意思是撩逗,詞性偏向於貶義,有輕薄的意思,但它被網絡化以後,已經從貶義詞轉爲中性詞。"
print(s)
multi_s = """\
牀前明月光,疑是地上霜。
舉頭望明月,低頭思故鄉。
"""
print(multi_s)

在這裏插入圖片描述

python字符串與編碼

所有的python文件首兩行內容都應該如下所示

#!/usr/bin/python
# -*- coding: UTF-8 -*-

第一行註釋是爲了告訴Linux/OS X系統,這是一個Python可執行程序,Windows系統會忽略這個註釋;
第二行註釋是爲了告訴Python解釋器,按照UTF-8編碼讀取源代碼,否則,你在源代碼中寫的中文輸出可能會有亂碼。當然我們的python文件必須全部以utf8的格式去保存

下面代碼展示了在python裏字符串如何編碼(字符串按照某種格式編碼成字節碼)和解碼(字節碼按照某種格式解碼成對應的字符串)。

s = "中文"
s_utf8_bytes = s.encode(encoding="utf8")
s_gbk_bytes = s.encode(encoding="gbk")
print("[{}]的utf8編碼即變成字節碼bytes是 {}".format(s,s_utf8_bytes))
print("[{}]的gbk編碼即變成字節碼bytes是 {}".format(s,s_gbk_bytes))
s_utf8 = s_utf8_bytes.decode(encoding="utf8")
s_gbk = s_gbk_bytes.decode(encoding="gbk")
print("字節碼 {} 轉換成utf8編碼後是 [{}]".format(s_utf8_bytes, s_utf8))
print("字節碼 {} 轉換成gbk編碼後是 [{}]".format(s_gbk_bytes, s_gbk))

在這裏插入圖片描述

python空值None

None是空值,是Python裏一個特殊的值。不是0也不是空字符串

python變量命名規則

以python代碼風格PEP8爲參考

  • 小寫字母拼寫,多個單詞用下劃線連接(注意不是駝峯式命名規則)
  • 類中受保護的實例屬性,應該以一個下劃線開頭
  • 類中私有的實例屬性,應該以兩個下劃線開頭

變量使用和類型轉換

  • type函數可對變量的類型進行檢查,如type(a)
  • 可以採用內置函數對變量類型進行強制轉換
  • int():將一個數值或字符串轉換成整數,可以指定進制
  • int("num_str", base=10)將base進制的數(字符串表示,指定進制後必須是字符串)轉換爲十進制
  • float():將一個數值或字符串轉換成浮點數
  • str():將指定的對象轉換成字符串形式
  • chr():將整數轉換成該編碼對應的字符串(一個字符)。參考java裏的char和int的互換
  • ord():將字符串(一個字符)轉換成對應的編碼(整數)。參考java裏的char和int的互換
  • hex():將十進制轉換爲十六進制(返回的是字符串)
  • oct():將八進制轉換爲十六進制(返回的是字符串)
  • bin():將十進制轉化爲二進制(返回的是字符串)

下面代碼展示了類型轉換的例子,其中以0x開頭的表示十六進制,以0o開頭的表示八進制,以0b開頭的表示二進制

print(int("11"))  # 11
print(int("11", base=2))  # 3
print(float("36.8"))  # 36.8
print(str(12))  # 12
print(chr(65))  # A
print(ord('A'))  # 65
print(hex(18))  # 0x12
print(oct(18))  # 0o22
print(bin(4))  # 0b100

輸入和輸出函數

  • input(): 輸入函數,可通過設置參數來增加輸入提示語句
  • print(): 輸出函數,可通過佔位符的方式格式化輸出,其中%s代表字符串,%d代表整數,%f代表小數,%%表示百分號(因爲百分號代表了佔位符)。當不清楚用%d還是用其他時,用%s總是會沒錯的。當然個人更傾向於使用string.format()函數來格式化輸出,更詳細的string.format()信息可參考python菜鳥教程-format函數
name = input("輸入你的名字: ")
age = int(input("輸入你的年齡: "))
salary = float(input("輸入你的月薪: "))
print("姓名是: %s\t年齡是: %d\t月薪是: %.2f\t" % (name, age, salary))
print("姓名是: {}\t年齡是: {}\t月薪是: {:.2f}\t".format(name, age, salary))
print("單獨的%")
print("%s 使用了佔位符, 顯示%%" % (name))
# print("%s 使用了佔位符, 顯示%" % (name))   #  ValueError: incomplete format

在這裏插入圖片描述

python運算符

這裏是參考Github上的python100天教程,上面列的非常詳細。
需要重點記憶的是 下標、切片、成員運算符、邏輯運算符、(複合)賦值運算符

運算符 描述
[] [:] 下標,切片
** 指數
~ + - 按位取反, 正負號
* / % // 乘,除,模,整除
+ - 加,減
>> << 右移,左移
& 按位與
^ \| 按位異或,按位或
<= < > >= 小於等於,小於,大於,大於等於
== != 等於,不等於
is is not 身份運算符
in not in 成員運算符
not or and 邏輯運算符
= += -= *= /= %= //= **= &= |= ^= >>= <<= (複合)賦值運算符

分支結構

語法很簡單就是 if elif elif else

age = int(input("輸入你的年齡: "))
if age < 18:
    print("你還未成年")
elif age < 50:
    print("你正值壯年")
elif age < 100:
    print("你可以退休了")
else:
    print("活寶一個")

循環結構

  • 第一種是for ... in ...循環
sum = 0
for x in range(11):
    sum += x
print(sum)  # 55
  • 第二種是while bool循環
i = 0
sum = 0
while i<11:
    sum +=i
    i+=1
print(sum)  # 55

以上這兩種循環都可以用break提前退出循環, continue結束本次循環進入到下個循環。

python字符串

我們在上面也提到了字符串的編碼以及如何定義多行字符串。
字符串和整型及浮點型不同,字符串是結構化的,所以有內置的方法和屬性。
我們知道轉義字符\,但是我們可以在轉義字符之前加r來取消轉義字符。

name = "\u5927\u6570\u636e"
print(name)  # 大數據
name = r"\u5927\u6570\u636e"
print(name)  # \u5927\u6570\u636e

在這裏插入圖片描述
也可以考慮使用repr(obj)函數,將對象轉化爲供解釋器讀取的形式
在這裏插入圖片描述

下面代碼展示了字符串的一些常用操作。更詳細的可參考python菜鳥教程-字符串

s = "abcdefg"
print(s[0:4])  # abcd
print(s[:-1])  # abcdef
print(s[::-1]) # gfedcba
print(len(s))  # 7
print(s.upper())  # ABCDEFG
print(s.lower())  # abcdefg
print(s.find("def"))  # 3
print(s.startswith("abc"))  # True
print(s.endswith("fg"))  # True
print("  abc  \n".strip())  # abc
操作或函數 描述
s[a:b] 按照前閉後開的原則,去字符串s從下標爲a開始到下標爲b結束的子串
s[:-1] 取字符串開始到結尾的子串即除去最後一個字符的子串。這是因爲字符串可以從0開始取第一個,也可以從-1開始取最後一個
s[::-1] 逆置字符串。等價於 ''.join(reversed("abcdefg"))reduce(lambda x,y: y+x, "abcdefg")
len(s) 獲取字符串長度
s.upper() 將字符串全部變成大寫,返回新字符串
s.lower() 將字符串全部變成小寫,返回新字符串
s.find(“def”) 尋找子串所在字符串的位置,找不到返回-1
s.startswith(“abc”) 判斷字符串是否以指定子串開始
s.endswith(“fg”) 判斷字符串是否以指定子串結束
s…strip() 取消字符串前後空格和回車符

列表

  • 列表初始化。用[]表示,多個數據用逗號隔開如l = [1, 2, 3, 4, 5]
  • 獲取列表長度。用len(list)
  • 遍歷列表。用enumerate()函數同時獲取下標和值
  • 獲取某個下標元素。用list[index]即用下標的方式
  • 在末尾添加元素。用list.append(obj)
  • 刪除元素。用list.pop(index),默認index=-1即默認刪除最後一個元素
  • 反轉列表。用list.reverse()
  • 清空列表。用l.clear()
  • 列表拼接。用list1+list2
  • 列表排序。用list.sort()函數或者用new_list=sorted()函數生成一個新的列表。兩者都需要一個key參數來排序。參數key是一個函數,該函數只有一個輸入參數及列表的元素。

下面代碼展示了列表的常用操作

# 初始化列表
l = [1, 2, 3, 4, 5]
# 獲取列表長度
print(len(l))
# 獲取指定下標的值
print(l[1])
# 遍歷列表 同時獲得下標和值
for i, v in enumerate(l):
    print("下標是 {}  值是 {}".format(i, v))
# 在列表末尾添加一個元素
l.append(6)
# 刪除一個元素, 默認參數是-1,即最後一個元素
l.pop()
# 反轉列表
l.reverse()
# 清空列表
l.clear()
# 列表拼接
print([1, 2] + [3, 4, 5])
# 列表排序
tp_list = [("patrick",26,100),("mata",28,300),("uzi",22,300),("faker",20,400),("rookie",21,500)]
# 用列表自帶方法排序
tp_list.sort(key=lambda x:x[1])
print(tp_list)
# 用內置方法sorted()函數給列表排序,生成新的有序列表,原來的列表順序不變
new_sorted_tp_list = sorted(tp_list,key=lambda x:x[1],reverse=True)
print(new_sorted_tp_list)

# 根據多個字段進行排序
tp_list.sort(key=lambda x:(x[2],x[1]))
print(tp_list)
# 多字段多規則排序 可以在字段前添加+、-
tp_list.sort(key=lambda x:(-x[2],x[1]))
print(tp_list)

生成式和生成器

先說下python語法裏的三元表達式 xx if bool_expression else yy ,當表達式爲True時返回xx否則返回yy
如下例所示。

res = "success" if 3 > 2 else "fail"
print(res)  # success
res = "success" if 3 > 4 else "fail"
print(res)  # fail

生成式創建列表創建的是collections.Iterable對象,生成器創建的是collections.Iterator對象。Iterator對象繼承於Iterable,只不過Iterator對象多了一個next()方法,可以類比Java裏的迭代器。從數目上看,Iterable對象的數目是固定可數的,Iterator是通過next()方法不斷獲取的即可能是無限的(直到發生StopIteration異常才知道遍歷結束,當然可以通過for … in循環來避免該異常)。其中Iterable是可以多次遍歷的即多次for循環,但是Iterator遍歷一次之後就沒有數據了,想再次查看只能重新生成新的Iterator對象。

可以採用help(Iterable)查看幫助文檔
或者通過model.__file__查看模塊所在源碼路徑查看源碼。__file__是模塊的內置屬性,可以查看模塊所在文件路徑
在這裏插入圖片描述
判斷一個對象是否可以迭代

from collections import Iterable
isinstance([1,2,3],Iterable)

生成式創建列表的方式是通過[]生成器創建列表的方式是通過()。而且由於生成器創建的是Iterator對象,它存儲的是迭代方法,並沒有完全存儲所有的數據,故而生成器創建列表佔用內存遠小於生成式創建列表所佔用的內存。可以通過sys.getsizeof(obj)查看對象佔用內存大小。

# 生成式創建列表 前面的x就是新生成的列表的元素 if是過濾條件
f = [x for x in range(1, 11) if x % 2 == 0]
print(f)  # [2, 4, 6, 8, 10]
print(sys.getsizeof(f))  # 128
# 這裏構造兩層for循環 x+Y就是新生成的列表的元素 if是過濾條件
f = [x + y for x in 'ABCDE' for y in '1234567' if x=='A' and y in ['2','4']]
print(f)  # ['A2', 'A4']
print(sys.getsizeof(f))  # 96

# 生成器列表
f = (x for x in range(1, 11) if x % 2 == 0)
print(f)  # <generator object <genexpr> at 0x0000015092FBB150>
print(sys.getsizeof(f))  # 88
for x in f:
    print(x)

在這裏插入圖片描述

還有一種通過yield關鍵字來使得一個函數變成一個生成器函數。可以將yield理解爲return,下次迭代時就從yield語句後再執行。關於更詳細的內容可以參考python菜鳥教程-yield解析
如下面代碼所示,獲取文件的每一行,然後通過生成器的方式去迭代返回,這樣就不會把文件的所有行全部存儲起來佔用大量內存了。

def read_line_file(fpath):
    with open(fpath, 'r', encoding="utf8") as f:
        while True:
            s = f.readline()
            if s:
                yield s
            else:
                return


for s in read_line_file("d:/a.txt"):
    print(s.strip())  # 注意我這裏使用了strip()函數消除掉了後面的回車符這樣才能保證輸出內容和文件內容一致

假設文件d:/a.txt內容如下,輸出結果和文件內容一致

這是第一行
這是第二行
這是第三行
這是第四行
這是第五行

元組

類似於列表,只不過元組裏的元素無法改變,一樣可通過for in進行遍歷。
可通過list(tuple)將元組變成list,也可以通過tuple(list)將列表變成tuple。
由於元組的不可修改特性,所以能用元組的地方就儘量用元組。

# 創建一個tuple
tp = ("patrick", 26)
# 根據tuple創建list
tp_list = list(tp)
print(tp_list)  # ['patrick', 26]
# 根據list創建tuple
print(tuple([1,2,3]))  # (1, 2, 3)
# 遍歷tuple
for x in tp:
    print(x)

集合

  • 集合set中的元素是不能重複的。即元素需要是不可變對象
  • 創建集合使用{}set()方法,創建空集合必須使用set(),因爲{}用來創建字典。
  • 可以使用生成式生成集合。{num for num in range(1, 100) if num % 30 == 0}
  • 添加元素。s.add(x)
  • 刪除元素。s.discard(x)
  • 清空集合。s.clear()
  • 集合交併差(& | -)。運算符重載。
# 採用set()構造
s = set("abbccdefffg")
print(s)  # 已去重
# 採用生成式創建集合
set4 = {num for num in range(1, 100) if num % 30 == 0}
# 添加元素
s.add('h')
# 刪除元素
s.discard('a')
# 清空集合
s.clear()

字典

字典dict是由鍵值對組成,類似於Java裏的HashMap。字典dict的key必須是不可變對象,這點尤其重要,一般都用字符串作爲key。

  • 可通過{}或dict()方法或zip函數或生成式創建字典
  • 獲取指定key值。dict.get(key,default_value)
  • 更新指定key值。dict[k]=vdict.update(k1=v1, k2=v2)
  • 刪除指定key值。dict.pop(k,default_value)
  • 判斷指定key是否在dict裏。key in dict
  • 遍歷dict。for k,v in dict.items()
# 採用{}創建dict
info = {"name": "patrick", "age": 26}
print(info)  # {'name': 'patrick', 'age': 26}
# 採用dict()創建dict
info = dict(name="patrick", age=26)
print(info)  # {'name': 'patrick', 'age': 26}
# 通過zip函數將兩個序列壓成字典(zip函數生成的是個迭代器對象)
info = dict(zip(["patrick","marry","jeffery"],[26,27,28]))
print(info)  # {'patrick': 26, 'marry': 27, 'jeffery': 28}
# 通過生成式的方式創建dict
items3 = {num: num ** 2 for num in range(1, 5)}
print(items3)  # {1: 1, 2: 4, 3: 9, 4: 16}

# 獲取指定key對應的值,獲取不到返回默認值
print(info.get("patrick",11))  # 26
# 更新指定key值
info["patrick"]=99
print(info.get("patrick",11))  # 99
# 更新指定鍵值對
info.update(patrick=18, marry=19)
print(info)  # {'patrick': 18, 'marry': 19, 'jeffery': 28}
# 刪除指定key,並返回key對應的值,如找不到key則返回默認值
print(info.pop("patrick",100))  # 18
# 判定key是否在dict裏
print("marry" in info)
# 遍歷dict
for k,v in info.items():
    print("key={}  value={}".format(k,v))

和list比較,dict有以下幾個特點:
查找和插入的速度極快,不會隨着key的增加而變慢;
需要佔用大量的內存內存浪費多

而list相反:
查找和插入的時間隨着元素的增加而增加
佔用空間小浪費內存很少
所以,dict是用空間來換取時間的一種方法。

究其原因還是因爲在底層實現上list是由鏈表實現的,dict是由哈希表實現的

不可變對象與可變對象

Python 在 heap 中分配的對象分成兩類:可變對象和不可變對象。所謂可變對象是指,對象的內容是可變的,例如 list、dict等。而不可變的對象則相反,表示其內容不可變,如int、float、string、tuple。

  • 不可變對象。由於 Python 中的變量存放的是對象引用,所以對於不可變對象而言,儘管對象本身不可變,但變量的對象引用是可變的
    在這裏插入圖片描述
  • 可變對象。其對象的內容是可以變化的。當對象的內容發生變化時,變量的對象引用是不會變化的。
    在這裏插入圖片描述

python裏的函數

python用def關鍵字來定義函數,包括函數名、函數參數,使用return關鍵字來返回函數值,如果沒有顯示調用return,那麼該函數的返回值就是None
如下自定義一個絕對值函數my_abs

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x
空函數

可以通過pass關鍵字來充當一個佔位符先保證語法正確。後面再補上完整的函數實現。同樣的,pass關鍵字也可以用在其他如if或者for或者while結構裏。

def nop():
    pass
函數參數

Python的函數定義非常簡單,但靈活度卻非常大。除了正常定義的必選參數(即位置參數)外,還可以使用默認參數可變參數關鍵字參數,使得函數定義出來的接口,不但能處理複雜的參數,還可以簡化調用者的代碼。

值得注意的是參數設置順序:必選參數在前,默認參數在後,否則Python的解釋器會報錯(這也很好理解,如果兩個參數,第一個參數是默認參數,第二個參數是必選參數,但是我在調用時只填寫一個參數,那麼這個參數到底是必選參數還是默認參數呢)。

默認參數

默認參數的一個問題:默認參數必須要設置成不可變對象。爲什麼要設計str、None這樣的不變對象呢?因爲不變對象一旦創建,對象內部的數據就不能修改,這樣就減少了由於修改數據導致的錯誤。此外,由於對象不變,多任務環境下同時讀取對象不需要加鎖,同時讀一點問題都沒有。我們在編寫程序時,如果可以設計一個不變對象,那就儘量設計成不變對象
參考下面的代碼,展示瞭如何定義函數(帶有默認參數),以及默認參數不是不可變對象的疑惑解釋。

def enroll(name, gender, age=6, city='Beijing'):
    print("name={}\tgender={}\tage={}\tcity={}".format(name, gender, age, city))


# 使用默認參數city
enroll("patrick","boy",age=18)  # name=patrick	gender=boy	age=18	city=Beijing
# 調用函數
enroll("marry","girl",age=19,city="ShenZhen")  # name=marry	gender=girl	age=19	city=ShenZhen
# 可以通過名字匹配的方式不用強制保證默認參數的調用次序
enroll("jackie","boy",city="WuHan",age=20)  # name=jackie	gender=boy	age=20	city=WuHan


# 此處函數的默認參數是不可變對象list
def add_end(l=[]):
    l.append("END")
    return l


# 第一次調用會給list增加一個"END"
print(add_end())  # ['END']
# 第二次調用會給list再增加一個"END" 所以就是兩個END"
print(add_end())  # ['END', 'END']


# 上面的例子就違背了初衷, 應該按照如下設計
# None是不可變對象
def add_end(l=None):
    if not l:
        l = []
    l.append("END")
    return l


print(add_end())  # ['END']
print(add_end())  # ['END']
可選參數

可選參數就是將多個參數變成一個元組tuple的形式傳入給函數,用*args表示可選參數。
下面代碼就展示了用可選參數的方式累計求和

# 定義可選參數, 用*args表示,args就是一個tuple
def sum_all(*args):
    print(type(args))  # <class 'tuple'>
    sum = 0
    for x in args:
        sum += x
    return sum


# 傳入多個參數作爲可選參數
print(sum_all(1, 2, 3, 4))  # 10
l = [1, 2, 3, 4, 5]
# 用列表的形式傳入作爲可選參數,注意添加*  因爲list可以轉爲tuple
print(sum_all(*l))  # 15
# 用tuple的形式傳入作爲可選參數,注意添加*
t = (1, 2, 3, 4, 5)
print(sum_all(*t))  # 15
關鍵字參數

關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝爲一個dict
**kwargs來定義關鍵字參數。

下面代碼展示了關鍵字參數函數的定義及調用

# 定義關鍵字參數 通過**kwargs來定義
def person(name, gender, **kwargs):
    print(type(kwargs))  # <class 'dict'>
    print("name={}\tgender={}\tother={}".format(name, gender, kwargs))


# 通過k=v來設置關鍵字參數
person("patirck", "boy", age=20, city="shenzhen")
info = {"age": 26, "city": "shenzhen"}
# 通過字典來設置關鍵字參數, 注意調用的時候在dict前加上**
person("patirck", "boy", **info)
命名關鍵字參數

對於關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數。如果要限制關鍵字參數的名字,就可以用命名關鍵字參數。
注意命名關鍵字的參數必須要全部填寫否則會報錯,有默認值的除外。
可通過在位置參數後添加一個* 來創建命名關鍵字參數,或者是已經有可選參數,後面的都是關鍵字參數。

下面的代碼就展示了命名關鍵字參數的使用。

# 通過命名關鍵字參數限制關鍵字參數的名字,即在位置參數後添加一個*  , *後面的參數被視爲命名關鍵字參數
# 注意命名關鍵字參數必須要全部填寫內容,否則會報錯,有默認值的除外
def person(name, gender, *, city, job):
    print(name, gender, city, job)

# 沒有填寫job所以報錯
# person("patirck", "boy", city="shenzhen")  # TypeError: person() missing 1 required keyword-only argument: 'job'
person("patirck", "boy", job="碼農", city="shenzhen")
d = {"job":"碼農", "city":"shenzhen"}
# 一樣可以通過dict來設置關鍵字參數
person("patirck", "boy", **d)
# person("patirck", "boy", age=20, city="shenzhen")  # TypeError: person() got an unexpected keyword argument 'age'

# 已經有可選參數了,所以就沒有必要按照上面的再添加* 可選參數後的就是命名關鍵字參數
def person(name, gender, *args, city, job):
    print(name, gender, args, city, job)

person("patirck", "boy", "11",12,job="碼農", city="shenzhen")  # patirck boy ('11', 12) shenzhen 碼農
# 沒有填寫必要的命名關鍵字參數所以報錯
# person("patirck", "boy", "11",12)  # TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
參數組合

參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。
雖然可以組合多達5種參數,但不要同時使用太多的組合,否則函數接口的可理解性很差。
重要的結論:對於任意函數,都可以通過類似func(*args, **kw)的形式調用它,無論它的參數是如何定義的。這也是後面裝飾函數的前提條件。
參數組合更詳細的內容可以參考廖雪峯老師的python教程–函數的參數

函數的參數總結

使用*args**kw是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。
命名的關鍵字參數是爲了限制調用者可以傳入的參數名,同時可以提供默認值
定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符*,否則定義的將是位置參數。

函數的參數檢查

數據類型檢查可以用內置函數isinstance()實現。
如下所示,檢查參數是否是整型或者浮點型,不是的話通過raise拋出異常TypeError並附上異常提示語句。

def my_abs(x):
    # 對傳入的參數進行類型檢查
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x


print(my_abs(-10))  # 10
# print(my_abs("abc"))  # TypeError: bad operand type
函數返回多個值

在python裏函數返回多個值其實是一個假象,返回的其實是一個tuple對象。

下面的代碼就很好地展示出了函數返回多個值的情況。

# 返回一個十進制數的十六進制、八進制、二進制 形式
def get_hex_oct_bin(x):
    if not isinstance(x, int):
        raise TypeError("參數類型必須是int")
    return hex(x), oct(x), bin(x)

# 分別用多個變量接收多個返回值
s1, s2, s3 = get_hex_oct_bin(18)
print(s1, s2, s3)  # 0x12 0o22 0b10010
# 用變量接收返回值tuple
t = get_hex_oct_bin(18)
print(type(t))  # <class 'tuple'>
print(t)  # ('0x12', '0o22', '0b10010')

python函數進階

匿名函數

python的匿名函數語法是 lambda [arg1 [,arg2,.....argn]]:expression。其中expression的值就是該匿名函數的返回值。
如果一個非常簡單的函數如求兩個數的和,沒有必要通過def去定義一個函數,可以直接用匿名函數來實現。

下面代碼就展示了匿名函數的定義及其使用。

# 將tuple變成str
print(str((3,4)))  # (3, 4)
# 通過變量str來接收匿名函數
str = lambda x, y: x + y
# 同時原先的內置函數str就被我們定義的匿名函數替代了
print(str(3, 4))  # 7

其實函數名也就是變量名,這也就是爲什麼變量的命令不能和python的關鍵字衝突的緣故。
函數在python裏也是一種變量(深刻理解這句話),是可以作爲函數參數和返回值的。

map/reduce/filter/sorted函數

可以聯想大數據MapReduce框架中的map方法和reduce方法。
map(func, *iterables) --> map object : 對Iterable中的每個元素使用func。如果可選參數的n個Iterable對象,那麼func的參數就是n個。
reduce(function, sequence[, initial]) -> value:對sequence中每個元素聚合,function的參數是2個,返回值是1個。
filter(function or None, iterable) --> filter object:對Iterable中的元素用function進行過濾。如果function是None則返回是True的元素。function的入參就是Iterable中的元素,返回值就是布爾值。
sorted(iterable, key=None, reverse=False):排序函數在上面講列表的時候用過,此處不再贅述。

# 採用map將Iterable裏的數據平方
m = map(lambda x:x**2, [1,2,3])
# map返回的是Iterator對象
print(isinstance(m, collections.Iterator))  # True
print(list(m))  # [1, 4, 9]
# 如果是多個Iterable的話,則一一對應去取值作爲func的參數
l = list(map(lambda x,y:x+y, [1,2,3], [4,5,6]))
print(l)  # [5, 7, 9]

# 採用reduce對Iterable裏的數據累積
from functools import reduce
r = reduce(lambda x,y:x*y, [1,2,3,4])
print(r)  # 24

# 採用filter過濾Iterable裏的數據 只有偶數才符合
f = filter(lambda x:x%2==0, range(1,11))
# map返回的是Iterator對象
print(isinstance(f, collections.Iterator))  # True
print(list(f))  # [2, 4, 6, 8, 10]
# 如果filter的函數是None的返回,則返回那些是True的元素
l = [ x for x in filter(None, [-1,0,1,2,3,"","abc"]) ]
print(l)  # [-1, 1, 2, 3, 'abc']

關於filter函數有個很實際的例子就是,運用埃氏篩法求素數。通過filter函數不斷地取過濾。

# -*- coding: UTF-8 -*-

def odd_iter():
    '''
    生成一個從3開始的奇數Iterator
    :return: Iterator
    '''
    n = 1
    while True:
        n += 2
        yield n

# 返回一個函數,判斷傳入函數的參數是否能被n整除
def filter_iter_by_prime_num(n):
    return lambda x: x % n != 0


def prime_iter():
    yield 2
    it = odd_iter()
    while True:
        num = next(it)
        yield num
        it = filter(filter_iter_by_prime_num(num), it)


if __name__ == '__main__':
    ct=0
    for i in prime_iter():
        if i > 100:
            break
        # print(i)
        ct+=1
    print("total count is {}".format(ct))

函數作爲返回值與閉包

首先我們應該對python中的變量有如下了解

  • python中的變量不需要聲明,變量的賦值操作即是變量聲明和定義的過程
  • 如果變量沒有賦值,則python認爲該變量不存在
  • 在函數之外定義的變量都可以稱爲全局變量。全局變量可以被文件內部的任何函數和外部文件訪問
  • 全局變量建議在文件的開頭定義
  • 也可以把全局變量放到一個專門的文件中,然後通過import來引用
  • 也可以定義自己的常量類
  • 局部變量可以粗糙地理解爲函數方法裏的變量

查看下面的例子,lazy_sum的返回值就是函數sum,且函數sum引用了局部變量。
這些局部變量都保存在返回的函數中,這就是閉包

# 將sum函數作爲lazy_sum函數的返回值
# 內部函數sum可以引用外部函數lazy_sum的參數和局部變量,當lazy_sum返回函數sum時,相關參數和變量都保存在返回的函數中,這種稱爲“閉包(Closure)”的程序結構擁有極大的威力
def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax

    return sum


f1 = lazy_sum(1, 2, 3, 4)
# 返回的函數f1並沒有立刻執行, 只有主動調用f1後纔會執行
print(f1())  # 10
f2 = lazy_sum(1, 2, 3, 4)
print(f2())  # 10
# 每次調用都會產生一個新的函數,所以兩者不相等
print(f1 == f2)  # False
print(type(f1))  # <class 'function'>

看看下面這個閉包的例子,是一個非常經典的例子。返回閉包時牢記一點:返回函數不要引用任何循環變量,或者後續會發生變化的變量

# 返回的函數中引用了循環變量
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs


f1, f2, f3 = count()
# 返回的結果並不是想象中的 1 4 9 這是因爲閉包引用了循環變量i
# 由於主動調用時纔會執行,但是此時所有的循環變量i都已經變成了3
print(f1(), f2(), f3())  # 9 9 9

如果一定要引用循環變量,方法是再創建一個函數,用該函數的參數綁定循環變量當前的值

def count():
    def f(j):
        def g():
            return j*j
        return g
        # return lambda :j*j
    fs = []
    for i in range(1, 4):
        fs.append(f(i))  # f(i)立刻被執行,因此i的當前值被傳入f()
    return fs


f1, f2, f3 = count()
print(f1(), f2(), f3())  # 1 4 9

用閉包實現一個計數器函數,每次調用一次,返回值就加一。
實現如下,閉包引用了列表(其實引用的是列表的地址可類比爲C++的指針),此處列表的引用是沒有變化的。

def create_counter():
    l = [0]

    def counter():
        l[0] = l[0] + 1
        return l[0]

    return counter


cc = create_counter()
print(cc())  # 1
print(cc())  # 2
print(cc())  # 3
python中的偏函數

偏函數是functools模塊提供的一個功能。當一個函數的參數太多時,需要簡化時,使用functools.partial可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。

下面的代碼就展示了偏函數的使用方法。創建一個將二進制數轉化爲十進制的方法。
當然也可以將自定義函數的某些參數值固定。

import functools
print(int("1110", base=2))  # 14
# 通過偏函數固定原先的int函數中的base參數值爲2
int2 = functools.partial(int, base=2)
print(int2("1110"))  # 14
python中的裝飾器

裝飾器是爲了在不改變源代碼的前提下增強函數功能的。它就是一個高階函數,以函數作爲入參,且以函數作爲返回值。
我們以上面寫的返回一個十進制數的十六進制、八進制、二進制 形式的函數get_hex_oct_bin爲例,來對該函數進行增強,譬如在日誌裏打印該函數的執行時間、返回結果、以及該函數的運行時間。

定義好裝飾器函數log後,在需要增強的函數上面添加@log即可完成對該函數的增強。
下面的@functools.wraps(func)是爲了解決函數的內置屬性__name__不會被log函數的返回值wrapper替代。如果不加這個執行print(get_hex_oct_bin.__name__)就會看到打印的是wrapper

import datetime
import time
import functools
def log(func):
    # 必須要添加該配置,否則被log裝飾的方法的__name__就是wrapper而不是原本方法名
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 以秒爲單位
        start = time.perf_counter()
        # 獲取格式化的當前時間
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print("{} start call {} ".format(current_time, func.__name__))
        res = func(*args,**kwargs)
        print("{} result value is {}".format(func.__name__, res))
        end = time.perf_counter()
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print("{} end call {} ".format(current_time, func.__name__))
        print("{} run time {} seconds".format(func.__name__, (end-start)))
        return res
    return wrapper

# 返回一個十進制數的十六進制、八進制、二進制 形式
@log
def get_hex_oct_bin(x):
    if not isinstance(x, int):
        raise TypeError("參數類型必須是int")
    # 睡眠5秒
    time.sleep(5)
    return hex(x), oct(x), bin(x)


print(get_hex_oct_bin.__name__)  # get_hex_oct_bin
get_hex_oct_bin(18)

增強後的結果如下圖所示
在這裏插入圖片描述

類比Java裏的註解,我們可以在裝飾器裏添加參數。只是在上面的裝飾器函數基礎上在嵌套一層函數即可。

def enhance_log(**text):
    def dec(func):
        # 必須要添加該配置,否則被log裝飾的方法的__name__就是wrapper而不是原本方法名
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("text dict on [{}] is {}".format(func.__name__, text))
            # 以秒爲單位
            start = time.perf_counter()
            # 獲取格式化的當前時間
            current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print("{} start call {} ".format(current_time, func.__name__))
            res = func(*args, **kwargs)
            print("{} result value is {}".format(func.__name__, res))
            end = time.perf_counter()
            current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print("{} end call {} ".format(current_time, func.__name__))
            print("{} run time {} seconds".format(func.__name__, (end - start)))
            return res

        return wrapper
    return dec


@enhance_log(desc="求和函數", version="v1.0")
def func1(x,y):
    print("{}+{}={}".format(x,y,x+y))
    return x+y

@enhance_log()
def func2(x,y):
    print("{}-{}={}".format(x,y,x-y))
    return x - y

func1(3,2)
func2(3,2)

運行結果如下所示。func1上的裝飾器上添加了參數,func2的裝飾器上沒有添加參數。
在這裏插入圖片描述

python模塊(module)與包(package)

在Python中,一個.py文件就稱之爲一個模塊(Module)。
爲了避免模塊名衝突,Python又引入了按目錄來組織模塊的方法,稱爲(Package)。

值得注意的是,每一個包目錄下面都會有一個__init__.py的文件,這個文件是必須存在的,否則,Python就把這個目錄當成普通目錄,而不是一個包。
__init__.py可以是空文件,也可以有Python代碼,因爲__init__.py本身就是一個模塊,而它的模塊名就是mycompany(以下面的目錄結構爲例)。
文件www.py的模塊名就是mycompany.web.www,兩個文件utils.py的模塊名分別是mycompany.utilsmycompany.web.utils

mycompany
 ├─ web
 │  ├─ __init__.py
 │  ├─ utils.py
 │  └─ www.py
 ├─ __init__.py
 ├─ abc.py
 └─ utils.py

自己創建模塊時要注意命名,不能和Python自帶的模塊名稱衝突。

python文件模板

下面是.py文件的模板。
第1行和第2行是標準註釋,第1行註釋可以讓這個hello.py文件直接在Unix/Linux/Mac上運行,第2行註釋表示.py文件本身使用標準UTF-8編碼;
第4行是一個字符串,表示模塊的文檔註釋,任何模塊代碼的第一個字符串都被視爲模塊的文檔註釋;可以使用module.__doc__訪問該模塊的文檔註釋;
第6行使用__author__變量把作者寫進去,這樣當你公開源代碼後別人就可以瞻仰你的大名;
倒數第二行if __name__=='__main__':只有在運行該模塊文件的時候纔會爲True,纔會執行test()方法,當要導入該模塊的時候該表達式爲False。

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

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()
作用域

在python中,爲了讓某些函數或者變量不被外界訪問或者說是將其變爲私有的(類比Java的private),是用_來實現的,實際上這是一種編程習慣。因爲Python並沒有一種方法可以完全限制訪問private函數或變量,但是,從編程習慣上不應該引用private函數或變量。
類似__xx__這樣的變量是特殊變量,如__author____name____doc__等,有特殊的用途,可以被直接訪問。但是我們不應該去定義這樣的變量,如果要定義私有變量,可以使用如_salary__salary的方式。

如下面的例子,_private_1_private_2都是私有函數,可以通過調用公有函數greeting來訪問。這是一種非常有用的代碼封裝和抽象的方法。
外部不需要引用的函數全部定義成private,只有外部需要引用的函數才定義爲public

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)
安裝第三方模塊

參考上面安裝IPython的方式通過pip去安裝第三方模塊。當然也可以選擇用Anaconda去安裝,但是這個比較麻煩,後面學到機器學習再去深究這個。

模塊搜索路徑

python模塊搜索的路徑順序是:程序主目錄、PYTHONPATH目錄、python的內置模塊和第三方庫。
可以通過sys.path能夠看到模塊搜索的路徑。
在這裏插入圖片描述
如果想添加自己的搜索目錄,有如下兩種方法

  • 通過sys.path.append("d:/test")添加自己的目錄,不過這種方法只在運行時有效,運行結束後就失效。如下圖所示,我的d:/test目錄下有一個prime.py文件,在添加目錄後能正確導入我的模塊。
    在這裏插入圖片描述
  • 通過設置PYTHONPATH環境變量。當然如果在linux環境下如果想永久有效的話可以將PYTHONPATH環境變量配置在如/etc/profile文件裏
# Windows環境下
# 獲取環境變量值
set PYTHONPATH
# 設置環境變量值
set PYTHONPATH=$PYTHONPATH;d:/test

在這裏插入圖片描述
這裏列出如何查看、刪除和更新環境變量的值。其實從代碼裏可以看出環境變量os.environ可以像字典那樣獲取和更新數據。只不過類型不是字典。

import os
from collections import Iterable

env_dict = os.environ
# 設置環境變量
env_dict.update(demo="Hello Wolrd")
# 獲取環境變量值
print(env_dict.get("demo"))  # Hello Wolrd
# 刪除環境變量
print(env_dict.pop("demo"))  # Hello Wolrd
print(env_dict.get("demo", "Nothing"))  # Nothing
print(isinstance(env_dict, Iterable))  # True
# 遍歷打印環境變量值
for k, v in os.environ.items():
    print("{}={}".format(k, v))

參考網址

廖雪峯老師的python教程
Github上的python100天教程
python菜鳥教程–關於可變對象與不可變對象
爲什麼python對象相同但是id卻不同
python如何定義常量類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章