【萬字長文】別再報班了,一篇文章帶你入門Python

本文始發於個人公眾號:TechFlow,原創不易,求個關注

最近有許多小夥伴後台聯繫我,說目前想要學習Python,但是沒有一份很好的資料入門。一方面的確現在市面上Python的資料過多,導致新手會不知如何選擇,另一個問題很多資料內容也很雜,從1+1到深度學習都包括,純粹關注Python本身語法的優質教材並不太多。

剛好我最近看到一份不錯的英文Python入門資料,我將它做了一些整理和翻譯寫下了本文。這份資料非常純粹,只有Python的基礎語法,專門針對想要學習Python的小白。

注釋

Python中用#表示單行注釋,#之後的同行的內容都會被注釋掉。

# Python中單行注釋用#表示,#之後同行字元全部認為被注釋。

使用三個連續的雙引號表示多行注釋,兩個多行注釋標識之間內容會被視作是注釋。

""" 與之對應的是多行注釋
    用三個雙引號表示,這兩段雙引號當中的內容都會被視作是注釋
"""

基礎變數類型與操作符

Python當中的數字定義和其他語言一樣:

#獲得一個整數
3
# 獲得一個浮點數
10.0

我們分別使用+, -, *, /表示加減乘除四則運算符。

1 + 1   # => 2
8 - 1   # => 7
10 * 2  # => 20
35 / 5  # => 7.0

這裡要注意的是,在Python2當中,10/3這個操作會得到3,而不是3.33333。因為除數和被除數都是整數,所以Python會自動執行整數的計算,幫我們把得到的商取整。如果是10.0 / 3,就會得到3.33333。目前Python2已經不再維護了,可以不用關心其中的細節。

但問題是Python是一個弱類型的語言,如果我們在一個函數當中得到兩個變數,是無法直接判斷它們的類型的。這就導致了同樣的計算符可能會得到不同的結果,這非常蛋疼。以至於程式設計師在運算除法的時候,往往都需要手工加上類型轉化符,將被除數轉成浮點數。

在Python3當中撥亂反正,修正了這個問題,即使是兩個整數相除,並且可以整除的情況下,得到的結果也一定是浮點數。

如果我們想要得到整數,我們可以這麼操作:

5 // 3       # => 1
-5 // 3      # => -2
5.0 // 3.0   # => 1.0 # works on floats too
-5.0 // 3.0  # => -2.0

兩個除號表示取整除,Python會為我們保留去除餘數的結果。

除了取整除操作之外還有取餘數操作,數學上稱為取模,Python中用%表示。

# Modulo operation
7 % 3  # => 1

Python中支援乘方運算,我們可以不用調用額外的函數,而使用**符號來完成:

# Exponentiation (x**y, x to the yth power)
2**3  # => 8

當運算比較複雜的時候,我們可以用括弧來強制改變運算順序。

# Enforce precedence with parentheses
1 + 3 * 2  # => 7
(1 + 3) * 2  # => 8

邏輯運算

Python中用首字母大寫的True和False表示真和假。

True  # => True
False  # => False

用and表示與操作,or表示或操作,not表示非操作。而不是C++或者是Java當中的&&, || 和!。

# negate with not
not True   # => False
not False  # => True

# Boolean Operators
# Note "and" and "or" are case-sensitive
True and False  # => False
False or True   # => True

在Python底層,True和False其實是1和0,所以如果我們執行以下操作,是不會報錯的,但是在邏輯上毫無意義。

# True and False are actually 1 and 0 but with different keywords
True + True # => 2
True * 8    # => 8
False - 5   # => -5

我們用==判斷相等的操作,可以看出來True==1, False == 0.

# Comparison operators look at the numerical value of True and False
0 == False  # => True
1 == True   # => True
2 == True   # => False
-5 != False # => True

我們要小心Python當中的bool()這個函數,它並不是轉成bool類型的意思。如果我們執行這個函數,那麼只有0會被視作是False,其他所有數值都是True

bool(0)     # => False
bool(4)     # => True
bool(-6)    # => True
0 and 2     # => 0
-5 or 0     # => -5

Python中用==判斷相等,>表示大於,>=表示大於等於, <表示小於,<=表示小於等於,!=表示不等。

# Equality is ==
1 == 1  # => True
2 == 1  # => False

# Inequality is !=
1 != 1  # => False
2 != 1  # => True

# More comparisons
1 < 10  # => True
1 > 10  # => False
2 <= 2  # => True
2 >= 2  # => True

我們可以用and和or拼裝各個邏輯運算:

# Seeing whether a value is in a range
1 < 2 and 2 < 3  # => True
2 < 3 and 3 < 2  # => False
# Chaining makes this look nicer
1 < 2 < 3  # => True
2 < 3 < 2  # => False

注意not,and,or之間的優先順序,其中not > and > or。如果分不清楚的話,可以用括弧強行改變運行順序。

list和字元串

關於list的判斷,我們常用的判斷有兩種,一種是剛才介紹的==,還有一種是is。我們有時候也會簡單實用is來判斷,那麼這兩者有什麼區別呢?我們來看下面的例子:

a = [1, 2, 3, 4]  # Point a at a new list, [1, 2, 3, 4]
b = a             # Point b at what a is pointing to
b is a            # => True, a and b refer to the same object
b == a            # => True, a's and b's objects are equal
b = [1, 2, 3, 4]  # Point b at a new list, [1, 2, 3, 4]
b is a            # => False, a and b do not refer to the same object
b == a            # => True, a's and b's objects are equal

Python是全引用的語言,其中的對象都使用引用來表示。is判斷的就是兩個引用是否指向同一個對象,而==則是判斷兩個引用指向的具體內容是否相等。舉個例子,如果我們把引用比喻成地址的話,is就是判斷兩個變數的是否指向同一個地址,比如說都是沿河東路XX號。而==則是判斷這兩個地址的收件人是否都叫張三。

顯然,住在同一個地址的人一定都叫張三,但是住在不同地址的兩個人也可以都叫張三,也可以叫不同的名字。所以如果a is b,那麼a == b一定成立,反之則不然。

Python當中對字元串的限制比較松,雙引號和單引號都可以表示字元串,看個人喜好使用單引號或者是雙引號。我個人比較喜歡單引號,因為寫起來方便。

字元串也支援+操作,表示兩個字元串相連。除此之外,我們把兩個字元串寫在一起,即使沒有+,Python也會為我們拼接:

# Strings are created with " or '
"This is a string."
'This is also a string.'

# Strings can be added too! But try not to do this.
"Hello " + "world!"  # => "Hello world!"
# String literals (but not variables) can be concatenated without using '+'
"Hello " "world!"    # => "Hello world!"

我們可以使用[]來查找字元串當中某個位置的字元,用len來計算字元串的長度。

# A string can be treated like a list of characters
"This is a string"[0]  # => 'T'

# You can find the length of a string
len("This is a string")  # => 16

我們可以在字元串前面加上f表示格式操作,並且在格式操作當中也支援運算,比如可以嵌套上len函數等。不過要注意,只有Python3.6以上的版本支援f操作。

# You can also format using f-strings or formatted string literals (in Python 3.6+)
name = "Reiko"
f"She said her name is {name}." # => "She said her name is Reiko"
# You can basically put any Python statement inside the braces and it will be output in the string.
f"{name} is {len(name)} characters long." # => "Reiko is 5 characters long."

最後是None的判斷,在Python當中None也是一個對象,所有為None的變數都會指向這個對象。根據我們前面所說的,既然所有的None都指向同一個地址,我們需要判斷一個變數是否是None的時候,可以使用is來進行判斷,當然用==也是可以的,不過我們通常使用is。

# None is an object
None  # => None

# Don't use the equality "==" symbol to compare objects to None
# Use "is" instead. This checks for equality of object identity.
"etc" is None  # => False
None is None   # => True

理解了None之後,我們再回到之前介紹過的bool()函數,它的用途其實就是判斷值是否是空。所有類型的默認空值會被返回False,否則都是True。比如0,””,[], {}, ()等。

# None, 0, and empty strings/lists/dicts/tuples all evaluate to False.
# All other values are True
bool(None)# => False
bool(0)   # => False
bool("")  # => False
bool([])  # => False
bool({})  # => False
bool(())  # => False

除了上面這些值以外的所有值傳入都會得到True。

變數與集合

輸入輸出

Python當中的標準輸入輸出是input和print

print會輸出一個字元串,如果傳入的不是字元串會自動調用__str__方法轉成字元串進行輸出。默認輸出會自動換行,如果想要以不同的字元結尾代替換行,可以傳入end參數:

# Python has a print function
print("I'm Python. Nice to meet you!")  # => I'm Python. Nice to meet you!

# By default the print function also prints out a newline at the end.
# Use the optional argument end to change the end string.
print("Hello, World", end="!")  # => Hello, World!

使用input時,Python會在命令行接收一行字元串作為輸入。可以在input當中傳入字元串,會被當成提示輸出:

# Simple way to get input data from console
input_string_var = input("Enter some data: ") # Returns the data as a string
# Note: In earlier versions of Python, input() method was named as raw_input()

變數

Python中聲明對象不需要帶上類型,直接賦值即可,Python會自動關聯類型,如果我們使用之前沒有聲明過的變數則會出發NameError異常。

# There are no declarations, only assignments.
# Convention is to use lower_case_with_underscores
some_var = 5
some_var  # => 5

# Accessing a previously unassigned variable is an exception.
# See Control Flow to learn more about exception handling.
some_unknown_var  # Raises a NameError

Python支援三元表達式,但是語法和C++不同,使用if else結構,寫成:

# if can be used as an expression
# Equivalent of C's '?:' ternary operator
"yahoo!" if 3 > 2 else 2  # => "yahoo!"

上段程式碼等價於:

if 3 > 2:
    return 'yahoo'
else:
    return 2

list

Python中用[]表示空的list,我們也可以直接在其中填充元素進行初始化:

# Lists store sequences
li = []
# You can start with a prefilled list
other_li = [4, 5, 6]

使用append和pop可以在list的末尾插入或者刪除元素:

# Add stuff to the end of a list with append
li.append(1)    # li is now [1]
li.append(2)    # li is now [1, 2]
li.append(4)    # li is now [1, 2, 4]
li.append(3)    # li is now [1, 2, 4, 3]
# Remove from the end with pop
li.pop()        # => 3 and li is now [1, 2, 4]
# Let's put it back
li.append(3)    # li is now [1, 2, 4, 3] again.

list可以通過[]加上下標訪問指定位置的元素,如果是負數,則表示倒序訪問。-1表示最後一個元素,-2表示倒數第二個,以此類推。如果訪問的元素超過數組長度,則會出發IndexError的錯誤。

# Access a list like you would any array
li[0]   # => 1
# Look at the last element
li[-1]  # => 3

# Looking out of bounds is an IndexError
li[4]  # Raises an IndexError

list支援切片操作,所謂的切片則是從原list當中拷貝出指定的一段。我們用start: end的格式來獲取切片,注意,這是一個左閉右開區間。如果留空表示全部獲取,我們也可以額外再加入一個參數表示步長,比如[1:5:2]表示從1號位置開始,步長為2獲取元素。得到的結果為[1, 3]。如果步長設置成-1則代表反向遍歷。

# You can look at ranges with slice syntax.
# The start index is included, the end index is not
# (It's a closed/open range for you mathy types.)
li[1:3]   # Return list from index 1 to 3 => [2, 4]
li[2:]    # Return list starting from index 2 => [4, 3]
li[:3]    # Return list from beginning until index 3  => [1, 2, 4]
li[::2]   # Return list selecting every second entry => [1, 4]
li[::-1]  # Return list in reverse order => [3, 4, 2, 1]
# Use any combination of these to make advanced slices
# li[start:end:step]

如果我們要指定一段區間倒序,則前面的start和end也需要反過來,例如我想要獲取[3: 6]區間的倒序,應該寫成[6:3:-1]。

只寫一個:,表示全部拷貝,如果用is判斷拷貝前後的list會得到False。可以使用del刪除指定位置的元素,或者可以使用remove方法。

# Make a one layer deep copy using slices
li2 = li[:]  # => li2 = [1, 2, 4, 3] but (li2 is li) will result in false.

# Remove arbitrary elements from a list with "del"
del li[2]  # li is now [1, 2, 3]

# Remove first occurrence of a value
li.remove(2)  # li is now [1, 3]
li.remove(2)  # Raises a ValueError as 2 is not in the list

insert方法可以指定位置插入元素,index方法可以查詢某個元素第一次出現的下標。

# Insert an element at a specific index
li.insert(1, 2)  # li is now [1, 2, 3] again

# Get the index of the first item found matching the argument
li.index(2)  # => 1
li.index(4)  # Raises a ValueError as 4 is not in the list

list可以進行加法運算,兩個list相加表示list當中的元素合併。等價於使用extend方法:

# You can add lists
# Note: values for li and for other_li are not modified.
li + other_li  # => [1, 2, 3, 4, 5, 6]

# Concatenate lists with "extend()"
li.extend(other_li)  # Now li is [1, 2, 3, 4, 5, 6]

我們想要判斷元素是否在list中出現,可以使用in關鍵字,通過使用len計算list的長度:

# Check for existence in a list with "in"
1 in li  # => True

# Examine the length with "len()"
len(li)  # => 6

tuple

tuple和list非常接近,tuple通過()初始化。和list不同,tuple是不可變對象。也就是說tuple一旦生成不可以改變。如果我們修改tuple,會引發TypeError異常。

# Tuples are like lists but are immutable.
tup = (1, 2, 3)
tup[0]      # => 1
tup[0] = 3  # Raises a TypeError

由於小括弧是有改變優先順序的含義,所以我們定義單個元素的tuple,末尾必須加上逗號,否則會被當成是單個元素:

# Note that a tuple of length one has to have a comma after the last element but
# tuples of other lengths, even zero, do not.
type((1))   # => <class 'int'>
type((1,))  # => <class 'tuple'>
type(())    # => <class 'tuple'>

tuple支援list當中絕大部分操作:

# You can do most of the list operations on tuples too
len(tup)         # => 3
tup + (4, 5, 6)  # => (1, 2, 3, 4, 5, 6)
tup[:2]          # => (1, 2)
2 in tup         # => True

我們可以用多個變數來解壓一個tuple:

# You can unpack tuples (or lists) into variables
a, b, c = (1, 2, 3)  # a is now 1, b is now 2 and c is now 3
# You can also do extended unpacking
a, *b, c = (1, 2, 3, 4)  # a is now 1, b is now [2, 3] and c is now 4
# Tuples are created by default if you leave out the parentheses
d, e, f = 4, 5, 6  # tuple 4, 5, 6 is unpacked into variables d, e and f
# respectively such that d = 4, e = 5 and f = 6
# Now look how easy it is to swap two values
e, d = d, e  # d is now 5 and e is now 4

解釋一下這行程式碼:

a, *b, c = (1, 2, 3, 4)  # a is now 1, b is now [2, 3] and c is now 4

我們在b的前面加上了星號,表示這是一個list。所以Python會在將其他變數對應上值的情況下,將剩下的元素都賦值給b。

補充一點,tuple本身雖然是不可變的,但是tuple當中的可變元素是可以改變的。比如我們有這樣一個tuple:

a = (3, [4])

我們雖然不能往a當中添加或者刪除元素,但是a當中含有一個list,我們可以改變這個list類型的元素,這並不會觸發tuple的異常:

a[1].append(0) # 這是合法的

dict

dict也是Python當中經常使用的容器,它等價於C++當中的map,即存儲key和value的鍵值對。我們用{}表示一個dict,用:分隔key和value。

# Dictionaries store mappings from keys to values
empty_dict = {}
# Here is a prefilled dictionary
filled_dict = {"one": 1, "two": 2, "three": 3}

dict的key必須為不可變對象,所以list、set和dict不可以作為另一個dict的key,否則會拋出異常:

# Note keys for dictionaries have to be immutable types. This is to ensure that
# the key can be converted to a constant hash value for quick look-ups.
# Immutable types include ints, floats, strings, tuples.
invalid_dict = {[1,2,3]: "123"}  # => Raises a TypeError: unhashable type: 'list'
valid_dict = {(1,2,3):[1,2,3]}   # Values can be of any type, however.

我們同樣用[]查找dict當中的元素,我們傳入key,獲得value,等價於get方法。

# Look up values with []
filled_dict["one"]  # => 1
filled_dict.get('one') #=> 1

我們可以call dict當中的keys和values方法,獲取dict當中的所有key和value的集合,會得到一個list。在Python3.7以下版本當中,返回的結果的順序可能和插入順序不同,在Python3.7及以上版本中,Python會保證返回的順序和插入順序一致:

# Get all keys as an iterable with "keys()". We need to wrap the call in list()
# to turn it into a list. We'll talk about those later.  Note - for Python
# versions <3.7, dictionary key ordering is not guaranteed. Your results might
# not match the example below exactly. However, as of Python 3.7, dictionary
# items maintain the order at which they are inserted into the dictionary.
list(filled_dict.keys())  # => ["three", "two", "one"] in Python <3.7
list(filled_dict.keys())  # => ["one", "two", "three"] in Python 3.7+

# Get all values as an iterable with "values()". Once again we need to wrap it
# in list() to get it out of the iterable. Note - Same as above regarding key
# ordering.
list(filled_dict.values())  # => [3, 2, 1]  in Python <3.7
list(filled_dict.values())  # => [1, 2, 3] in Python 3.7+

我們也可以用in判斷一個key是否在dict當中,注意只能判斷key。

# Check for existence of keys in a dictionary with "in"
"one" in filled_dict  # => True
1 in filled_dict      # => False

如果使用[]查找不存在的key,會引發KeyError的異常。如果使用get方法則不會引起異常,只會得到一個None

# Looking up a non-existing key is a KeyError
filled_dict["four"]  # KeyError

# Use "get()" method to avoid the KeyError
filled_dict.get("one")      # => 1
filled_dict.get("four")     # => None
# The get method supports a default argument when the value is missing
filled_dict.get("one", 4)   # => 1
filled_dict.get("four", 4)  # => 4

setdefault方法可以為不存在的key插入一個value,如果key已經存在,則不會覆蓋它:

# "setdefault()" inserts into a dictionary only if the given key isn't present
filled_dict.setdefault("five", 5)  # filled_dict["five"] is set to 5
filled_dict.setdefault("five", 6)  # filled_dict["five"] is still 5

我們可以使用update方法用另外一個dict來更新當前dict,比如a.update(b)。對於a和b交集的key會被b覆蓋,a當中不存在的key會被插入進來:

# Adding to a dictionary
filled_dict.update({"four":4})  # => {"one": 1, "two": 2, "three": 3, "four": 4}
filled_dict["four"] = 4         # another way to add to dict

我們一樣可以使用del刪除dict當中的元素,同樣只能傳入key。

Python3.5以上的版本支援使用**來解壓一個dict:

{'a': 1, **{'b': 2}}  # => {'a': 1, 'b': 2}
{'a': 1, **{'a': 2}}  # => {'a': 2}

set

set是用來存儲不重複元素的容器,當中的元素都是不同的,相同的元素會被刪除。我們可以通過set(),或者通過{}來進行初始化。注意當我們使用{}的時候,必須要傳入數據,否則Python會將它和dict弄混。

# Sets store ... well sets
empty_set = set()
# Initialize a set with a bunch of values. Yeah, it looks a bit like a dict. Sorry.
some_set = {1, 1, 2, 2, 3, 4}  # some_set is now {1, 2, 3, 4}

set當中的元素也必須是不可變對象,因此list不能傳入set。

# Similar to keys of a dictionary, elements of a set have to be immutable.
invalid_set = {[1], 1}  # => Raises a TypeError: unhashable type: 'list'
valid_set = {(1,), 1}

可以調用add方法為set插入元素:

# Add one more item to the set
filled_set = some_set
filled_set.add(5)  # filled_set is now {1, 2, 3, 4, 5}
# Sets do not have duplicate elements
filled_set.add(5)  # it remains as before {1, 2, 3, 4, 5}

set還可以被認為是集合,所以它還支援一些集合交叉並補的操作。

# Do set intersection with &
# 計算交集
other_set = {3, 4, 5, 6}
filled_set & other_set  # => {3, 4, 5}

# Do set union with |
# 計算並集
filled_set | other_set  # => {1, 2, 3, 4, 5, 6}

# Do set difference with -
# 計算差集
{1, 2, 3, 4} - {2, 3, 5}  # => {1, 4}

# Do set symmetric difference with ^
# 這個有點特殊,計算對稱集,也就是去掉重複元素剩下的內容
{1, 2, 3, 4} ^ {2, 3, 5}  # => {1, 4, 5}

set還支援超集和子集的判斷,我們可以用大於等於和小於等於號判斷一個set是不是另一個的超集或子集:

# Check if set on the left is a superset of set on the right
{1, 2} >= {1, 2, 3} # => False

# Check if set on the left is a subset of set on the right
{1, 2} <= {1, 2, 3} # => True

和dict一樣,我們可以使用in判斷元素在不在set當中。用copy可以拷貝一個set。

# Check for existence in a set with in
2 in filled_set   # => True
10 in filled_set  # => False

# Make a one layer deep copy
filled_set = some_set.copy()  # filled_set is {1, 2, 3, 4, 5}
filled_set is some_set        # => False

控制流和迭代

判斷語句

Python當中的判斷語句非常簡單,並且Python不支援switch,所以即使是多個條件,我們也只能羅列if-else

# Let's just make a variable
some_var = 5

# Here is an if statement. Indentation is significant in Python!
# Convention is to use four spaces, not tabs.
# This prints "some_var is smaller than 10"
if some_var > 10:
    print("some_var is totally bigger than 10.")
elif some_var < 10:    # This elif clause is optional.
    print("some_var is smaller than 10.")
else:                  # This is optional too.
    print("some_var is indeed 10.")

循環

我們可以用in來循環迭代一個list當中的內容,這也是Python當中基本的循環方式。


"""
For loops iterate over lists
prints:
    dog is a mammal
    cat is a mammal
    mouse is a mammal
"""
for animal in ["dog", "cat", "mouse"]:
    # You can use format() to interpolate formatted strings
    print("{} is a mammal".format(animal))

如果我們要循環一個範圍,可以使用range。range加上一個參數表示從0開始的序列,比如range(10),表示[0, 10)區間內的所有整數:

"""
"range(number)" returns an iterable of numbers
from zero to the given number
prints:
    0
    1
    2
    3
"""
for i in range(4):
    print(i)

如果我們傳入兩個參數,則代表迭代區間的首尾

"""
"range(lower, upper)" returns an iterable of numbers
from the lower number to the upper number
prints:
    4
    5
    6
    7
"""
for i in range(4, 8):
    print(i)

如果我們傳入第三個元素,表示每次循環變數自增的步長

"""
"range(lower, upper, step)" returns an iterable of numbers
from the lower number to the upper number, while incrementing
by step. If step is not indicated, the default value is 1.
prints:
    4
    6
"""
for i in range(4, 8, 2):
    print(i)

如果使用enumerate函數,可以同時迭代一個list的下標和元素

"""
To loop over a list, and retrieve both the index and the value of each item in the list
prints:
    0 dog
    1 cat
    2 mouse
"""
animals = ["dog", "cat", "mouse"]
for i, value in enumerate(animals):
    print(i, value)

while循環和C++類似,當條件為True時執行,為false時退出。並且判斷條件不需要加上括弧:

"""
While loops go until a condition is no longer met.
prints:
    0
    1
    2
    3
"""
x = 0
while x < 4:
    print(x)
    x += 1  # Shorthand for x = x + 1

捕獲異常

Python當中使用try和except捕獲異常,我們可以在except後面限制異常的類型。如果有多個類型可以寫多個except,還可以使用else語句表示其他所有的類型。finally語句內的語法無論是否會觸發異常都必定執行

# Handle exceptions with a try/except block
try:
    # Use "raise" to raise an error
    raise IndexError("This is an index error")
except IndexError as e:
    pass                 # Pass is just a no-op. Usually you would do recovery here.
except (TypeError, NameError):
    pass                 # Multiple exceptions can be handled together, if required.
else:                    # Optional clause to the try/except block. Must follow all except blocks
    print("All good!")   # Runs only if the code in try raises no exceptions
finally:                 #  Execute under all circumstances
    print("We can clean up resources here")

with操作

在Python當中我們經常會使用資源,最常見的就是open打開一個文件。我們打開了文件句柄就一定要關閉,但是如果我們手動來編碼,經常會忘記執行close操作。並且如果文件異常,還會觸發異常。這個時候我們可以使用with語句來代替這部分處理,使用with會自動在with塊執行結束或者是觸發異常時關閉打開的資源

以下是with的幾種用法和功能:

# Instead of try/finally to cleanup resources you can use a with statement
# 代替使用try/finally語句來關閉資源
with open("myfile.txt") as f:
    for line in f:
        print(line)

# Writing to a file
# 使用with寫入文件
contents = {"aa": 12, "bb": 21}
with open("myfile1.txt", "w+") as file:
    file.write(str(contents))        # writes a string to a file

with open("myfile2.txt", "w+") as file:
    file.write(json.dumps(contents)) # writes an object to a file

# Reading from a file
# 使用with讀取文件
with open('myfile1.txt', "r+") as file:
    contents = file.read()           # reads a string from a file
print(contents)
# print: {"aa": 12, "bb": 21}

with open('myfile2.txt', "r+") as file:
    contents = json.load(file)       # reads a json object from a file
print(contents)     
# print: {"aa": 12, "bb": 21}

可迭代對象

凡是可以使用in語句來迭代的對象都叫做可迭代對象,它和迭代器不是一個含義。這裡只有可迭代對象的介紹,想要了解迭代器的具體內容,請移步傳送門:

Python——五分鐘帶你弄懂迭代器與生成器,夯實程式碼能力

當我們調用dict當中的keys方法的時候,返回的結果就是一個可迭代對象。

# Python offers a fundamental abstraction called the Iterable.
# An iterable is an object that can be treated as a sequence.
# The object returned by the range function, is an iterable.

filled_dict = {"one": 1, "two": 2, "three": 3}
our_iterable = filled_dict.keys()
print(our_iterable)  # => dict_keys(['one', 'two', 'three']). This is an object that implements our Iterable interface.

# We can loop over it.
for i in our_iterable:
    print(i)  # Prints one, two, three

我們不能使用下標來訪問可迭代對象,但我們可以用iter將它轉化成迭代器,使用next關鍵字來獲取下一個元素。也可以將它轉化成list類型,變成一個list。

# However we cannot address elements by index.
our_iterable[1]  # Raises a TypeError

# An iterable is an object that knows how to create an iterator.
our_iterator = iter(our_iterable)

# Our iterator is an object that can remember the state as we traverse through it.
# We get the next object with "next()".
next(our_iterator)  # => "one"

# It maintains state as we iterate.
next(our_iterator)  # => "two"
next(our_iterator)  # => "three"

# After the iterator has returned all of its data, it raises a StopIteration exception
next(our_iterator)  # Raises StopIteration

# We can also loop over it, in fact, "for" does this implicitly!
our_iterator = iter(our_iterable)
for i in our_iterator:
    print(i)  # Prints one, two, three

# You can grab all the elements of an iterable or iterator by calling list() on it.
list(our_iterable)  # => Returns ["one", "two", "three"]
list(our_iterator)  # => Returns [] because state is saved

函數

使用def關鍵字來定義函數,我們在傳參的時候如果指定函數內的參數名,可以不按照函數定義的順序傳參:

# Use "def" to create new functions
def add(x, y):
    print("x is {} and y is {}".format(x, y))
    return x + y  # Return values with a return statement

# Calling functions with parameters
add(5, 6)  # => prints out "x is 5 and y is 6" and returns 11

# Another way to call functions is with keyword arguments
add(y=6, x=5)  # Keyword arguments can arrive in any order.

可以在參數名之前加上*表示任意長度的參數,參數會被轉化成list:

# You can define functions that take a variable number of
# positional arguments
def varargs(*args):
    return args

varargs(1, 2, 3)  # => (1, 2, 3)

也可以指定任意長度的關鍵字參數,在參數前加上**表示接受一個dict:

# You can define functions that take a variable number of
# keyword arguments, as well
def keyword_args(**kwargs):
    return kwargs

# Let's call it to see what happens
keyword_args(big="foot", loch="ness")  # => {"big": "foot", "loch": "ness"}

當然我們也可以兩個都用上,這樣可以接受任何參數:

# You can do both at once, if you like
def all_the_args(*args, **kwargs):
    print(args)
    print(kwargs)
"""
all_the_args(1, 2, a=3, b=4) prints:
    (1, 2)
    {"a": 3, "b": 4}
"""

傳入參數的時候我們也可以使用*和**來解壓list或者是dict:

# When calling functions, you can do the opposite of args/kwargs!
# Use * to expand tuples and use ** to expand kwargs.
args = (1, 2, 3, 4)
kwargs = {"a": 3, "b": 4}
all_the_args(*args)            # equivalent to all_the_args(1, 2, 3, 4)
all_the_args(**kwargs)         # equivalent to all_the_args(a=3, b=4)
all_the_args(*args, **kwargs)  # equivalent to all_the_args(1, 2, 3, 4, a=3, b=4)

Python中的參數可以返回多個值

# Returning multiple values (with tuple assignments)
def swap(x, y):
    return y, x  # Return multiple values as a tuple without the parenthesis.
                 # (Note: parenthesis have been excluded but can be included)

x = 1
y = 2
x, y = swap(x, y)     # => x = 2, y = 1
# (x, y) = swap(x,y)  # Again parenthesis have been excluded but can be included.

函數內部定義的變數即使和全局變數重名,也不會覆蓋全局變數的值。想要在函數內部使用全局變數,需要加上global關鍵字,表示這是一個全局變數:

# Function Scope
x = 5

def set_x(num):
    # Local var x not the same as global variable x
    x = num    # => 43
    print(x)   # => 43

def set_global_x(num):
    global x
    print(x)   # => 5
    x = num    # global var x is now set to 6
    print(x)   # => 6

set_x(43)
set_global_x(6)

Python支援函數式編程,我們可以在一個函數內部返回一個函數:

# Python has first class functions
def create_adder(x):
    def adder(y):
        return x + y
    return adder

add_10 = create_adder(10)
add_10(3)   # => 13

Python中可以使用lambda表示匿名函數,使用:作為分隔,:前面表示匿名函數的參數,:後面的是函數的返回值:

# There are also anonymous functions
(lambda x: x > 2)(3)                  # => True
(lambda x, y: x ** 2 + y ** 2)(2, 1)  # => 5

我們還可以將函數作為參數使用map和filter,實現元素的批量處理和過濾。關於Python中map、reduce和filter的使用,具體可以查看之前的文章:

五分鐘帶你了解map、reduce和filter

# There are built-in higher order functions
list(map(add_10, [1, 2, 3]))          # => [11, 12, 13]
list(map(max, [1, 2, 3], [4, 2, 1]))  # => [4, 2, 3]

list(filter(lambda x: x > 5, [3, 4, 5, 6, 7]))  # => [6, 7]

我們還可以結合循環和判斷語來給list或者是dict進行初始化:

# We can use list comprehensions for nice maps and filters
# List comprehension stores the output as a list which can itself be a nested list
[add_10(i) for i in [1, 2, 3]]         # => [11, 12, 13]
[x for x in [3, 4, 5, 6, 7] if x > 5]  # => [6, 7]

# You can construct set and dict comprehensions as well.
{x for x in 'abcddeef' if x not in 'abc'}  # => {'d', 'e', 'f'}
{x: x**2 for x in range(5)}  # => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

模組

使用import語句引入一個Python模組,我們可以用.來訪問模組中的函數或者是類。

# You can import modules
import math
print(math.sqrt(16))  # => 4.0

我們也可以使用from import的語句,單獨引入模組內的函數或者是類,而不再需要寫出完整路徑。使用from import *可以引入模組內所有內容(不推薦這麼干)

# You can get specific functions from a module
from math import ceil, floor
print(ceil(3.7))   # => 4.0
print(floor(3.7))  # => 3.0

# You can import all functions from a module.
# Warning: this is not recommended
from math import *

可以使用as給模組內的方法或者類起別名:

# You can shorten module names
import math as m
math.sqrt(16) == m.sqrt(16)  # => True

我們可以使用dir查看我們用的模組的路徑:

# You can find out which functions and attributes
# are defined in a module.
import math
dir(math)

這麼做的原因是如果我們當前的路徑下也有一個叫做math的Python文件,那麼會覆蓋系統自帶的math的模組。這是尤其需要注意的,不小心會導致很多奇怪的bug。

我們來看一個完整的類,相關的介紹都在注釋當中

# We use the "class" statement to create a class
class Human:

    # A class attribute. It is shared by all instances of this class
    # 類屬性,可以直接通過Human.species調用,而不需要通過實例
    species = "H. sapiens"

    # Basic initializer, this is called when this class is instantiated.
    # Note that the double leading and trailing underscores denote objects
    # or attributes that are used by Python but that live in user-controlled
    # namespaces. Methods(or objects or attributes) like: __init__, __str__,
    # __repr__ etc. are called special methods (or sometimes called dunder methods)
    # You should not invent such names on your own.
    # 最基礎的構造函數
    # 加了下劃線的函數和變數表示不應該被用戶使用,其中雙下劃線的函數或者是變數將不會被子類覆蓋
    # 前後都有雙下劃線的函數和屬性是類當中的特殊屬性
    def __init__(self, name):
        # Assign the argument to the instance's name attribute
        self.name = name

        # Initialize property
        self._age = 0

    # An instance method. All methods take "self" as the first argument
    # 類中的函數,所有實例可以調用,第一個參數必須是self
    # self表示實例的引用
    def say(self, msg):
        print("{name}: {message}".format(name=self.name, message=msg))

    # Another instance method
    def sing(self):
        return 'yo... yo... microphone check... one two... one two...'

    # A class method is shared among all instances
    # They are called with the calling class as the first argument
    @classmethod
    # 加上了註解,表示是類函數
    # 通過Human.get_species來調用,所有實例共享
    def get_species(cls):
        return cls.species

    # A static method is called without a class or instance reference
    @staticmethod
    # 靜態函數,通過類名或者是實例都可以調用
    def grunt():
        return "*grunt*"

    # A property is just like a getter.
    # It turns the method age() into an read-only attribute of the same name.
    # There's no need to write trivial getters and setters in Python, though.
    @property
    # property註解,類似於get,set方法
    # 效率很低,除非必要,不要使用
    def age(self):
        return self._age

    # This allows the property to be set
    @age.setter
    def age(self, age):
        self._age = age

    # This allows the property to be deleted
    @age.deleter
    def age(self):
        del self._age

以上內容的詳細介紹之前也有過相關文章,可以查看:

Python——slots,property和對象命名規範

下面我們來看看Python當中類的使用:

# When a Python interpreter reads a source file it executes all its code.
# This __name__ check makes sure this code block is only executed when this
# module is the main program.
# 這個是main函數也是整個程式入口的慣用寫法
if __name__ == '__main__':
    # Instantiate a class
    # 實例化一個類,獲取類的對象
    i = Human(name="Ian")
    # 執行say方法
    i.say("hi")                     # "Ian: hi"
    j = Human("Joel")
    j.say("hello")                  # "Joel: hello"
    # i和j都是Human的實例,都稱作是Human類的對象
    # i and j are instances of type Human, or in other words: they are Human objects

    # Call our class method
    # 類屬性被所有實例共享,一旦修改全部生效
    i.say(i.get_species())          # "Ian: H. sapiens"
    # Change the shared attribute
    Human.species = "H. neanderthalensis"
    i.say(i.get_species())          # => "Ian: H. neanderthalensis"
    j.say(j.get_species())          # => "Joel: H. neanderthalensis"

    # 通過類名調用靜態方法
    # Call the static method
    print(Human.grunt())            # => "*grunt*"

    # Cannot call static method with instance of object 
    # because i.grunt() will automatically put "self" (the object i) as an argument
    # 不能通過對象調用靜態方法,因為對象會傳入self實例,會導致不匹配
    print(i.grunt())                # => TypeError: grunt() takes 0 positional arguments but 1 was given

    # Update the property for this instance
    # 實例級別的屬性是獨立的,各個對象各自擁有,修改不會影響其他對象內的值
    i.age = 42
    # Get the property
    i.say(i.age)                    # => "Ian: 42"
    j.say(j.age)                    # => "Joel: 0"
    # Delete the property
    del i.age
    # i.age                         # => this would raise an AttributeError

這裡解釋一下,實例和對象可以理解成一個概念,實例的英文是instance,對象的英文是object。都是指類經過實例化之後得到的對象。

繼承

繼承可以讓子類繼承父類的變數以及方法,並且我們還可以在子類當中指定一些屬於自己的特性,並且還可以重寫父類的一些方法。一般我們會將不同的類放在不同的文件當中,使用import引入,一樣可以實現繼承。

from human import Human

# Specify the parent class(es) as parameters to the class definition
class Superhero(Human):

    # If the child class should inherit all of the parent's definitions without
    # any modifications, you can just use the "pass" keyword (and nothing else)
    # but in this case it is commented out to allow for a unique child class:
    # pass
    # 如果要完全繼承父類的所有的實現,我們可以使用關鍵字pass,表示跳過。這樣不會修改父類當中的實現

    # Child classes can override their parents' attributes
    species = 'Superhuman'

    # Children automatically inherit their parent class's constructor including
    # its arguments, but can also define additional arguments or definitions
    # and override its methods such as the class constructor.
    # This constructor inherits the "name" argument from the "Human" class and
    # adds the "superpower" and "movie" arguments:
    # 子類會完全繼承父類的構造方法,我們也可以進行改造,比如額外增加一些參數
    def __init__(self, name, movie=False,
                 superpowers=["super strength", "bulletproofing"]):

        # add additional class attributes:
        # 額外新增的參數
        self.fictional = True
        self.movie = movie
        # be aware of mutable default values, since defaults are shared
        self.superpowers = superpowers

        # The "super" function lets you access the parent class's methods
        # that are overridden by the child, in this case, the __init__ method.
        # This calls the parent class constructor:
        # 子類可以通過super關鍵字調用父類的方法
        super().__init__(name)

    # override the sing method
    # 重寫父類的sing方法
    def sing(self):
        return 'Dun, dun, DUN!'

    # add an additional instance method
    # 新增方法,只屬於子類
    def boast(self):
        for power in self.superpowers:
            print("I wield the power of {pow}!".format(pow=power))
if __name__ == '__main__':
    sup = Superhero(name="Tick")

    # Instance type checks
    # 檢查繼承關係
    if isinstance(sup, Human):
        print('I am human')
    # 檢查類型
    if type(sup) is Superhero:
        print('I am a superhero')

    # Get the Method Resolution search Order used by both getattr() and super()
    # This attribute is dynamic and can be updated
    # 查看方法查詢的順序
    # 先是自身,然後沿著繼承順序往上,最後到object
    print(Superhero.__mro__)    # => (<class '__main__.Superhero'>,
                                # => <class 'human.Human'>, <class 'object'>)

    # 相同的屬性子類覆蓋了父類
    # Calls parent method but uses its own class attribute
    print(sup.get_species())    # => Superhuman

    # Calls overridden method
    # 相同的方法也覆蓋了父類
    print(sup.sing())           # => Dun, dun, DUN!

    # Calls method from Human
    # 繼承了父類的方法
    sup.say('Spoon')            # => Tick: Spoon

    # Call method that exists only in Superhero
    # 子類特有的方法
    sup.boast()                 # => I wield the power of super strength!
                                # => I wield the power of bulletproofing!

    # Inherited class attribute
    sup.age = 31
    print(sup.age)              # => 31

    # Attribute that only exists within Superhero
    print('Am I Oscar eligible? ' + str(sup.movie))

多繼承

我們創建一個蝙蝠類:


# Another class definition
# bat.py
class Bat:

    species = 'Baty'

    def __init__(self, can_fly=True):
        self.fly = can_fly

    # This class also has a say method
    def say(self, msg):
        msg = '... ... ...'
        return msg

    # And its own method as well
    # 蝙蝠獨有的聲吶方法
    def sonar(self):
        return '))) ... ((('

if __name__ == '__main__':
    b = Bat()
    print(b.say('hello'))
    print(b.fly)

我們再創建一個蝙蝠俠的類,同時繼承Superhero和Bat:

# And yet another class definition that inherits from Superhero and Bat
# superhero.py
from superhero import Superhero
from bat import Bat

# Define Batman as a child that inherits from both Superhero and Bat
class Batman(Superhero, Bat):

    def __init__(self, *args, **kwargs):
        # Typically to inherit attributes you have to call super:
        # super(Batman, self).__init__(*args, **kwargs)      
        # However we are dealing with multiple inheritance here, and super()
        # only works with the next base class in the MRO list.
        # So instead we explicitly call __init__ for all ancestors.
        # The use of *args and **kwargs allows for a clean way to pass arguments,
        # with each parent "peeling a layer of the onion".
        # 通過類名調用兩個父類各自的構造方法
        Superhero.__init__(self, 'anonymous', movie=True, 
                           superpowers=['Wealthy'], *args, **kwargs)
        Bat.__init__(self, *args, can_fly=False, **kwargs)
        # override the value for the name attribute
        self.name = 'Sad Affleck'

    # 重寫父類的sing方法
    def sing(self):
        return 'nan nan nan nan nan batman!'

執行這個類:

if __name__ == '__main__':
    sup = Batman()

    # Get the Method Resolution search Order used by both getattr() and super().
    # This attribute is dynamic and can be updated
    # 可以看到方法查詢的順序是先沿著superhero這條線到human,然後才是bat
    print(Batman.__mro__)       # => (<class '__main__.Batman'>, 
                                # => <class 'superhero.Superhero'>, 
                                # => <class 'human.Human'>, 
                                # => <class 'bat.Bat'>, <class 'object'>)

    # Calls parent method but uses its own class attribute
    # 只有superhero有get_species方法
    print(sup.get_species())    # => Superhuman

    # Calls overridden method
    print(sup.sing())           # => nan nan nan nan nan batman!

    # Calls method from Human, because inheritance order matters
    sup.say('I agree')          # => Sad Affleck: I agree

    # Call method that exists only in 2nd ancestor
    # 調用蝙蝠類的聲吶方法
    print(sup.sonar())          # => ))) ... (((

    # Inherited class attribute
    sup.age = 100
    print(sup.age)              # => 100

    # Inherited attribute from 2nd ancestor whose default value was overridden.
    print('Can I fly? ' + str(sup.fly)) # => Can I fly? False

進階

生成器

我們可以通過yield關鍵字創建一個生成器,每次我們調用的時候執行到yield關鍵字處則停止。下次再次調用則還是從yield處開始往下執行:

# Generators help you make lazy code.
def double_numbers(iterable):
    for i in iterable:
        yield i + i

# Generators are memory-efficient because they only load the data needed to
# process the next value in the iterable. This allows them to perform
# operations on otherwise prohibitively large value ranges.
# NOTE: `range` replaces `xrange` in Python 3.
for i in double_numbers(range(1, 900000000)):  # `range` is a generator.
    print(i)
    if i >= 30:
        break

除了yield之外,我們還可以使用()小括弧來生成一個生成器:

# Just as you can create a list comprehension, you can create generator
# comprehensions as well.
values = (-x for x in [1,2,3,4,5])
for x in values:
    print(x)  # prints -1 -2 -3 -4 -5 to console/terminal

# You can also cast a generator comprehension directly to a list.
values = (-x for x in [1,2,3,4,5])
gen_to_list = list(values)
print(gen_to_list)  # => [-1, -2, -3, -4, -5]

關於生成器和迭代器更多的內容,可以查看下面這篇文章:

五分鐘帶你弄懂迭代器與生成器,夯實程式碼能力

裝飾器

我們引入functools當中的wraps之後,可以創建一個裝飾器。裝飾器可以在不修改函數內部程式碼的前提下,在外面包裝一層其他的邏輯:

# Decorators
# In this example `beg` wraps `say`. If say_please is True then it
# will change the returned message.
from functools import wraps

def beg(target_function):
    @wraps(target_function)
    # 如果please為True,額外輸出一句Please! I am poor :(
    def wrapper(*args, **kwargs):
        msg, say_please = target_function(*args, **kwargs)
        if say_please:
            return "{} {}".format(msg, "Please! I am poor :(")
        return msg

    return wrapper

@beg
def say(say_please=False):
    msg = "Can you buy me a beer?"
    return msg, say_please

print(say())                 # Can you buy me a beer?
print(say(say_please=True))  # Can you buy me a beer? Please! I am poor :(

裝飾器之前也有專門的文章詳細介紹,可以移步下面的傳送門:

一文搞定Python裝飾器,看完面試不再慌

結尾

不知道有多少小夥伴可以看到結束,原作者的確非常厲害,把Python的基本操作基本上都囊括在裡面了。如果都能讀懂並且理解的話,那麼Python這門語言就算是入門了。

原作者寫的是一個Python文件,所有的內容都在Python的注釋當中。我在它的基礎上做了修補和額外的描述。如果想要獲得原文,可以點擊查看原文,或者是在公眾號內回復learnpython獲取。

如果你之前就有其他語言的語言基礎,我想本文讀完應該不用30分鐘。當然在30分鐘內學會一門語言是不可能的,也不是我所提倡的。但至少通過本文我們可以做到熟悉Python的語法,知道大概有哪些操作,剩下的就要我們親自去寫程式碼的時候去體會和運用了。

根據我的經驗,在學習一門新語言的前期,不停地查閱資料是免不了的。希望本文可以作為你在使用Python時候的查閱文檔。

今天的文章就到這裡,原創不易,需要你的一個關注,你的舉手之勞對我來說很重要。