文章目錄
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
)、PyPy
、Jython
、IronPython
。用的最廣泛的還是CPython
,但是如果是交互式的話個人建議用IPython
,IPython
可以通過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]=v
或dict.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.utils
和mycompany.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如何定義常量類