這篇文章持續更新一些學習這門課時文法方面踩過的坑/遇到的問題,如果有錯誤的地方歡迎交流指正。
文章目錄
- 數字numbers
- 字元串string
- 清單list
-
- 切片slice
- 清單生成式list comprehension
- 元組tuple
- 集合set
- 字典dictionary
- 一些内置方法
- 檔案處理
- 關于類與繼承
-
- 類的屬性
- 類的方法
- 一些常見的困惑
-
- 關于main
- 關于Python命名規則
- 什麼是Foo?
數字numbers
- 關于float和int的強制類型轉換:
- float()方法可以接受的參數類型包括代表整數/浮點數的字元串、整數
- int()方法可以接收到參數類型包括代表整數的字元串、浮點數
- 如果将一個代表浮點數的字元串傳給
,那麼會引起int()
ValueError
>>> int('5') 5 >>> float('5.0') 5.0 >>> float('5') 5.0 >>> int(5.0) 5 >>> float(5) 5.0 >>> int('5.0') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '5.0' >>> int(float('5.0')) 5
- 将數字按n位輸出(補0)的兩種方式,請看下面例子:
# 将數字num按6位輸出,差位補0 num = 123 # 方法1:f-string格式控制 print(f'{num:06}') # 000123 # 如果是 print(f'{num:6}') 則輸出 123 # 方法2:str.zfill() num_str = str(num) result = num_str.zfill(6) # 字元串右對齊補0 print(result) # 000123
-
将n進制(2 <= n <= 36)數字num轉化為10進制數輸出:
可以使用int(str, base)方法,
接收一個代表數字的字元串,str
接收這個數字所使用的進制。該方法傳回一個int類型的數字base
>>> num1 = 101010 >>> print(int(str(num1), 2)) 42 >>> num2 = 123456 >>> print(int(str(num2), 7)) 22875 >>> num3_str = '123456' >>> t = int(num3_str, 7) >>> print(t) 22875 >>> print(type(t)) <class 'int'>
-
round() 函數的進位準則
奇進偶舍是一種比較精确比較科學的計數保留法,是一種數字修約規則。具體要求如下(以保留兩位小數為例):
- 要求保留位數的後一位如果是4或者4以下的數字,則舍去, 例如 5.214保留兩位小數為5.21。
- 如果保留位數的後一位如果是6或者6以上的數字,則進上去, 例如5.216保留兩位小數為5.22。
- 如果是一位小數保留整數部分 且 小數部分是5,則根據整數部分個位數(奇進偶舍)決定保留結果。
- 如果是n位小數保留n-1位,且第n位小數是5,則要根據第n-1位的小數(奇進偶舍)決定保留結果。
- 如果保留位數的後一位如果是5,且該位數後有數字,則應進上去。例如5.2152保留兩位小數為5.22,5.2252保留兩位小數為5.23,5.22500001保留兩位小數為5.23。
>>> round(5.215,2)#實際并沒有進位 5.21 >>> round(5.225,2) 5.22 >>> >>> round(1.5)#此處進位 2 >>> round(1.5)==round(2.5)#偶數舍去 True >>> round(1.15,1) 1.1 >>> round(1.25,1) 1.2 >>> round(1.151,1) 1.2 >>> round(1.251,1) 1.3
- 從統計學的角度,“奇進偶舍”比“四舍五入”要科學,在大量運算時,它使舍入後的結果誤差的均值趨于零,而不是像四舍五入那樣逢五就入,導緻結果偏向大數,使得誤差産生積累進而産生系統誤差,“奇進偶舍”使測量結果受到舍入誤差的影響降到最低。
- NB:Python由于浮點數的精度問題,它不是嚴格的“奇進偶舍”,例如官方文檔中的例子:
The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.
至于原因,可以參考Python中關于round函數的小坑中的解釋:這跟浮點數的精度有關。我們知道在機器中浮點數不一定能精确表達,因為換算成一串1和0後可能是無限位數的,機器已經做出了截斷處理。那麼在機器中儲存的2.675這個數字就比實際數字要小那麼一點點。這一點點就導緻了它離2.67要更近一點點,是以保留兩位小數時就近似到了2.67。
字元串string
- str.issapce():如果字元串中隻包含空白,則傳回 True,否則傳回 False
- 避免在循環中用+和+=操作符來累加字元串. 由于字元串是不可變的, 這樣做會建立不必要的臨時對象, 并且導緻二次方而不是線性的運作時間. 作為替代方案, 你可以将每個子串加入清單, 然後在循環結束後用
連接配接清單. (也可以将每個子串寫入一個 cStringIO.StringIO 緩存中.).join
-
fstring是Python3.6版本之後一種很靈活的控制字元串輸出的方式,這篇博文有很詳細的介紹。
關于如何用變量指定浮點數輸出寬度,可以參考下面這種做法:
num = 12345.678 width = 10 # 保留兩位小數 print(f"{num:.2f}") # 12345.68 # 保留兩位小數,width個占位符,不足的使用0補充 print(f"{num:0{width}.2f}") # 0012345.68
-
對于Python的字元串中意味着什麼?[:-1]
>>> 'test\n' 'test\n' >>> 'test\n'[:-1] 'test'
因為它對空字元串也是有效的,是以它在移除 假若存在的最末位的字元(包括\n\r\t等) 時非常安全
切片不僅對字元串有效,對任何序列都有效。
對于檔案中的line,可以使用
來删除最後的換行符。但有時候檔案最後一行并不是以換行符結束(而是以EOF-end of file),這時就可以用切片來移除末尾行最後任意一個符号。line.rstrip('\n')
-
在 str.rstrip([chars]) 中,當chars由n個字元組成時(即chars=char1char2char3…charn),不能将chars視作一個字元串。
傳回的新字元串會把str末尾所有屬于chars的char都删除,直到遇到一個非chars的字元為止。
同理str.lstrip([chars])
s1 = "[email protected]'@#@x@#x" print(s1.rstrip('@#x')) # 輸出結果是"[email protected]'"而不是"[email protected]'@#@x" print(s1.rstrip('@''#''x')) # 輸出結果是"[email protected]'"而不是"[email protected]"或"[email protected]@#@x" # 下面這兩種寫法是錯誤的 # print(s1.rstrip('@', '#', 'x')) # 會報錯TypeError: rstrip expected at most 1 arguments, got 3 # print(s1.rstrip(['@', '#', 'x'])) # 會報錯TypeError: rstrip arg must be None or str
- index() 方法檢測字元串中是否包含子字元串 str ,如果指定 beg(開始) 和 end(結束) 範圍,則檢查是否包含在指定範圍内,在的話傳回索引位置。該方法與 python find()方法一樣,隻不過如果str不在 string中會報一個異常,而find方法不在的話傳回-1。
清單list
切片slice
- 清單的append()方法傳回值是None Type,它隻能用于修改原清單,無法連續嵌套使用,例如這樣的寫法是不合法的
list1 = [1] list1.append(2).append(3) # 錯誤寫法
- 清單切片中的深複制與淺複制,請看下面代碼
a = [1, 2, 3, 4, 5, 6] b = a[:3] # 淺複制,修改清單a的值不影響清單b的值 c = a # 深複制,清單a與清單c指向同一處記憶體位址,其中一個改變,另一個随着改變 a[:3] = [6, 7, 8] print(a) # [6, 7, 8, 4, 5, 6] print(b) # [1, 2, 3] print(c) # [6, 7, 8, 4, 5, 6]
-
二維數組的切片:對一個二維數組,想取其中的某一列元素,該怎麼操作呢?
請看下面這個例子:
# 現有一個二維數組a,想取其中第二列元素[2, 5, 6] a =[[1,2,3], [4,5,6], [7,8,9]] # 先來看幾個錯誤的嘗試: print(a[0:3][1:2]) # [[4, 5, 6]] # 上面那行代碼實際上是先對a做了切片[0:3]得到[[1,2,3],[4,5,6],[7,8,9]],再對其做切片[1:2]得到[[4, 5, 6]] # 同理下面這行代碼是先對a做了切片[1:2]得到[[4,5,6]],再對其做切片[0:3]得到[[4, 5, 6]] print(a[1:2][0:3]) # # 這種寫法也是錯誤的: # print(a[:, 1]) # TypeError: list indices must be integers or slices, not tuple # 正确的寫法: b = [i[1] for i in a] print(b) # [2, 5, 8]
清單生成式list comprehension
清單生成式/清單推導式/清單解析式:清單推導式提供了從序列建立清單的簡單途徑。通常應用程式将一些操作應用于某個序列的每個元素,用其獲得的結果作為生成新清單的元素,或者根據确定的判定條件建立子序列。
每個清單推導式都在 for 之後跟一個表達式(表達式可以是任意的,意思是你可以在清單中放入任意類型的對象),然後有零到多個 for 或 if 子句(各語句之間是嵌套關系)。傳回結果是一個根據表達從其後的 for 和 if 上下文環境中生成出來的清單。
另:如果希望表達式推導出一個元組,此時元組的括号不可省略
# 對每個元素進行操作
>>> seq = [2, 4, 6]
>>> [3*x for x in seq]
[6, 12, 18]
# 生成清單嵌套清單
>>> [[x, x**2] for x in seq]
[[2, 4], [4, 16], [6, 36]]
# 對序列中每個元素調用某個方法
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
# 用if子句對序列元素進行過濾
>>> [3*x for x in seq if x > 3]
[12, 18]
>>> [3*x for x in seq if x < 2]
[]
# 不同元素來自于不同序列
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]
# 清單推導式可以使用複雜表達式或者嵌套函數
>>> [str(round(355/113, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']
清單推導式的執行順序:左邊第二個語句是最外層,依次往右進一層;左邊第一條語句是最後一層。
# 清單推導式的執行順序:左邊第二個語句是最外層,依次往右進一層;左邊第一條語句是最後一層。
[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]
# 執行順序
list1 = []
for x in range(1,5):
if x > 2:
for y in range(1,4):
if y < 3:
list1.append(x * y)
# 該清單為[3, 6, 4, 8]
在一個清單生成式中,for前面的if … else是表達式,而for後面的if是過濾條件,不能帶else
[x if x % 2 == 0 else -x for x in range(1, 11)]
# 輸出[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
[x if x % 2 == 0 for x in range(1, 11)]
# 報錯
# File "<stdin>", line 1
# [x if x % 2 == 0 for x in range(1, 11)]
# ^
# SyntaxError: invalid syntax
元組tuple
- 元組用"()"辨別,内部元素用逗号隔開。但是元組不能二次指派,相當于隻讀清單
集合set
-
與s.update( "字元串" )
含義不同:s.update( {"字元串"} )
将字元串添加到集合中,有重複的會忽略。s.update( {"字元串"} )
将字元串包含的每個字元逐個添加到集合中,有重複的會忽略。s.update( "字元串" )
>>> s = set(("Google", "Runoob", "Taobao")) >>> print(s) {'Google', 'Runoob', 'Taobao'} >>> s.update({"Facebook"}) >>> print(s) {'Google', 'Runoob', 'Taobao', 'Facebook'} >>> s.update("Yahoo") >>> print(s) {'h', 'o', 'Facebook', 'Google', 'Y', 'Runoob', 'Taobao', 'a'} >>>
-
不改變原集合隻傳回取交集的結果s.intersection()
改變原集合,不傳回值s.intersection_update()
與s.difference()
、s.difference_update()
與s.symmetric_difference()
同理s.symmetric_difference_update()
s.union()
不改變原集合隻傳回取并集的結果
注意,沒有
方法,想将原集合修改為與其他集合取并集的結果應使用s.union_update()
方法s.update()
>>> a = {1,2,3} >>> b = {3,4,5} >>> a.intersection(b) {3} >>> a {1, 2, 3} >>> a.intersection_update(b) >>> a {3} >>> a.union(b) {3, 4, 5} >>> a {3} >>> a.union_update(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'set' object has no attribute 'union_update' >>> a.update(b) >>> a {3, 4, 5}
字典dictionary
- Python 字典 items() 方法以 ‘dict_items’ 類型傳回可周遊的(鍵, 值) 元組數組。
常用dict.items()
來周遊字典for k, v in dict.items()
- 清單是有序的對象結合,字典是無序的對象集合。兩者之間的差別在于:字典當中的元素是通過鍵來存取的,而不是通過偏移存取
- 字典的key值必須是可哈希的
-
關于字典的參考資料:
Python3如何将兩個清單合并成字典
Python字典 你必須知道的用法系列
list,tuple,dict的資料結構
一些内置方法
- enumerate() 函數用于将一個可周遊的資料對象(如清單、元組或字元串)組合為一個索引序列(對象),同時列出資料和資料下标,一般用在 for 循環當中
enumerate(sequence, [start=0])
>>>seasons = ['Spring', 'Summer', 'Fall', 'Winter'] >>> list(enumerate(seasons)) [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')] >>> list(enumerate(seasons, start=1)) # 下标從 1 開始 [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')] >>> seq = ['one', 'two', 'three'] >>> for i, element in enumerate(seq): ... print(i, element) ... 0 one 1 two 2 three
- zip()函數用于将可疊代的對象作為參數,将對象中對應的元素打包成一個個元組,然後傳回由這些元組組成的一個可疊代對象,這樣做的好處是節約了不少的記憶體
zip([iterable, ...])
機器學習模型訓練中,經常需要打亂資料集,用 zip() 函數可以實作如下:list(zip([iterable, ...]))
import random X = [1, 2, 3, 4, 5, 6] y = [0, 1, 0, 0, 1, 1] zipped_data = list(zip(X, y)) # 将樣本和标簽一 一對應組合起來,并轉換成list類型友善後續打亂操作 random.shuffle(zipped_data) # 使用random子產品中的shuffle函數打亂清單,原地操作,沒有傳回值 new_zipped_data = list(map(list, zip(*zipped_data))) # zip(*)反向解壓,map()逐項轉換類型,list()做最後轉換 new_X, new_y = new_zipped_data[0], new_zipped_data[1] # 傳回打亂後的新資料 print('X:',X,'\n','y:',y) print('new_X:',new_X, '\n', 'new_y:',new_y)
-
range() 的負數step與reversed() 函數
range()函數傳回的是一個可疊代對象(非清單類型)
range(stop)
range的step參數為負數的邏輯是:先按正序的左閉右開區間取值,然後再反序輸出。而不是在左開右閉的區間裡反序取值range(start, stop[, step])
# for i in range(a,b,-1) # 當a > b時,需要加第三個參數-1,代表倒序輸出 # 倒序輸出range()時,仍然是在左閉右開區間内倒序,即取不到第二個參數 for a in range(5,-3,-1): print(a) # 輸出5到-2
reversed()函數傳回一個反轉的疊代器。
可以對range(a,b)使用reversed(),相當與倒序周遊[a,b)
for b in reversed(range(-3, 5)): print(b) # 輸出4到-3
-
sorted()函數 week4重點
sorted() 是Python3的内置函數,它可以對所有可疊代的對象進行排序操作,并且不改變原對象的值,傳回一個新的排序後的清單。
sorted(iterable, key=None[, reverse=False])
其中key可以為lambda函數
如果想實作由大到小排序,可以令key值為負。也可以通過傳入第三個參數 reverse=True來實作反向排序。
如果需要依靠多個元素排序,則用括号括起,依次聲明。例如:
i代表傳入的第一個參數(一個可疊代對象,這裡是dict1.items())中的每一個元素。
NB: sort() 是應用在list上的方法,它具有如下特性:
- 這個方法會修改原始的 list(傳回值為None)
- 通常這個方法不如sorted()友善
- 如果你不需要原始的 list,list.sort()方法效率會稍微高一些
檔案處理
- f.readlines()傳回的是一個清單,包含檔案中包含的所有行。
# 打開一個檔案 f = open("/tmp/foo.txt", "r") str = f.readlines() print(str) # 關閉打開的檔案 f.close() # 輸出如下 # ['Python 是一個非常好的語言。\n', '是的,的确非常好!!\n']
-
open(filename, mode)
将 mode 設定為 w+ 或 a+ 時,發現直接進行讀操作,得到的内容都是空,但原因不太相同:
如果 mode 設定為 w+,即使沒有執行 write 操作,也會将檔案内容清空,是以這個時候直接進行讀草稿,讀到的是空内容。
f = open("E:\\administrator\\Desktop\\test.txt", "w+") ``` 如果 mode 設定為 a+,檔案指針位置預設在最後面,因為讀内容時,是按照指針的位置往後讀,是以若指針位置在最後,讀出來即是空。在讀之前,一定要注意确認好指針位置是對的。 ```python f = open("E:\\administrator\\Desktop\\test.txt", "a+") f.write("append content") print(f.tell()) #此時指針在檔案字元末尾處 f.seek(0) print(f.tell()) # 指針回到0的位置 str = f.read() print(str) f.close()f = open("E:\\administrator\\Desktop\\test.txt", "w+")
關于類與繼承
- Python中所有東西都是對象(objecet),每一個對象(objecet)都是由某個對象(objecet)建立的,每一個對象都是繼承自某種類型。有些對象(objecet)是類(class)
- 類,class,可以分為類的屬性和方法。Python中以雙下劃線開頭和結尾的屬性稱為特殊屬性,由于對象的方法也屬于屬性,是以以雙下劃線開頭和結尾的方法稱為特殊方法。
- 定義類的參數時,預設順序為:位置參數positional arguments,可變參數,預設參數。位置參數應該始終在可變參數之前,否則會被傳入到可變參數内,視為可變參數的一部分
- ?如果函數的參數清單第n位是
,那麼第n位後的參數必須以*
的形式傳入,否則就會報錯。可以用這種方法強制使用者輸入參數對應的值arg_name=arg
- 子類a想要繼承父類A,隻需要在定義子類時将父類名稱寫在子類名稱後的括号裡,例如:
class a(A): def __init__(self, name): self.name = name
- 子類會繼承父類的所有屬性和方法,可以重寫父類的屬性和方法,還可以定義屬于自己的屬性和方法。
- 一個類的執行個體被作為參數傳入另一個類時,該執行個體可以作為另一個類的屬性值。可以通過
來調用另一個類的執行個體.屬性(即類的執行個體).屬性的屬性(即類的執行個體的屬性)
-
可以用于判斷x是否為Class的一個執行個體,傳回True或Falseisinstance(x, Class)
類的屬性
- 通常我們都在類的__init__方法中定義類的屬性,文法
self.屬性名 = 屬性
- Python支援在類的外部添加和删除屬性
class Person: def __init__(self, name, age): self.name = name if age < 0: raise PersonException(“Age should be a positive integer!") self.age = age # 執行個體化一個student student_1 = Person('Tony', 22) # 給student_1添加屬性degree student_1.degree = 'Bachelor' # 删除student_1的age屬性 del student_1.age
-
傳回對象的名稱,比如類型(type, class)對象的名稱就是系統内置的或自定義的名稱字元串,類型的執行個體通常沒有屬性 __name__a.__name__
-
傳回的是類型(type),等效于a.__class__
type(a)
-
傳回的是類(class)a.__base__
-
元組,包含 類型對象(type, class) C 的全部基類,類型的執行個體通常沒有屬性__bases__a.__bases__
-
傳回a自身的各項屬性,但是不包括它從父類繼承的屬性a.__dict__
-
可以檢視a的所有屬性,包括自身屬性和從父類繼承的屬性dir(a)
- 子類的初始化函數
實際上是綁定在父類初始化函數上的.__init__
類的方法
-
等效于a.__repr__()
,會傳回<repr(a)
object at類名
>,它是面向開發者的,要求準确無誤的指向對象記憶體位址
-
等效于a.__str__()
,自定義傳回值時隻能是字元串,它是面向使用者的,具有更強的可讀性。print(a)
- 對于一個類a來說:
- 如果隻定義了__repr__方法,
,repr(a)
,a.__repr__()
,print(a)
都傳回.repr()方法的傳回值a.__str__()
- 如果隻定義了__str__方法,
,repr(a)
傳回<a.__repr__()
object at類名
>;記憶體位址
,print(a)
傳回.str()方法的傳回值a.__str__()
- 如果__repr__方法和__str__方法都有定義,則
,repr(a)
傳回.__repr__()方法的傳回值;a.__repr__()
,print(a)
傳回.__str__()方法的傳回值a.__str__()
- 如果隻定義了__repr__方法,
一些常見的困惑
關于main
一些人不明白為什麼要在主程式前寫
if __name__ == '__main__':
在函數調用時,目前的程式名稱是main,被調用的程式名稱是其“檔案名”。這樣在調用上述函數的時候,if條件不滿足,各種檢驗不會運作。
因為即使是一個打算被用作腳本的檔案,也應該是可導入的。所有的頂級代碼在子產品導入時都會被執行。但如果簡單的導入導緻這個腳本的主功能(main functionality)被執行,這是不必要的。
是以主功能應該放在一個main()函數中,防止該腳本被導入時主功能被執行。
NB:不要去 調用函數、建立對象或者執行那些不應該在使用pydoc時被執行的操作。
關于Python命名規則
module_name # 子產品名
package_name # 包名
ClassName # 類名
method_name # 方法名
ExceptionName # 異常名
function_name # 函數名
GLOBAL_VAR_NAME # 全局變量名
instance_var_name # 執行個體變量名
function_parameter_name # 函數參數名
local_var_name # 本地變量名
命名約定
- 所謂”内部(Internal)”表示僅子產品内可用, 或者, 在類内是保護或私有的
- 用單下劃線(_)開頭表示子產品變量或函數是protected的(使用from module import *時不會包含)
- 用雙下劃線(__)開頭的執行個體變量或方法表示類内私有
- 将相關的類和頂級函數放在同一個子產品裡。不像Java,沒必要限制一個類一個子產品。
- 對類名使用大寫字母開頭的單詞(如CapWords, 即Pascal風格),但是子產品名應該用小寫加下劃線的方式(如lower_with_under.py)。盡管已經有很多現存的子產品使用類似于CapWords.py這樣的命名,但現在已經不鼓勵這樣做,因為如果子產品名碰巧和類名一緻,這會讓人困擾
應該避免的名稱
- 單字元名稱,除了計數器和疊代器。
- 包/子產品名中的連字元(-)
- 雙下劃線開頭并結尾的名稱(Python保留,例如__init__)
什麼是Foo?
Foo / **Bar **is an intentionally meaningless placeholder word often used in computer programming.常被作為函數/方法的名稱或變量名