在 Y 分鐘內學會 Python

SlimeNull發表於2021-05-05

在 Y 分鐘內學會 Python

這是翻譯, 原文地址: Learn Python in Y Minutes

在 90 年代初, Python 由 Guido van Rossum 創造, 現在, 它是最受歡迎的程式語言之一. 因其簡明的語法, 我愛上了它. 語法基本上是可以執行的虛擬碼.

提示: 這篇文章適用於 Python 3, 如果你想要學習舊版 Python 2.7, 單擊這裡

# 單行註釋以 '#' 作為開頭

"""多行註釋可以使用三個雙引號
   並且經常用與書寫文件
"""

####################################################
## 1. 原始資料型別和操作符
####################################################

# 你可以使用數字
3  # 等同於 3

# 數學運算也是你希望使用的形式
1 + 1  # 結果是 2
8 - 1  # 結果是 7
10 * 2  # 結果是 20
35 / 5  # 結果是 7.0

# 正負數的整除都會向下取整
5 // 3  # 結果是 1
-5 // 3  # 結果是 -2
5.0 // 3.0  # 結果是 1.0 # 在浮點運算中也同樣生效
-5.0 // 3.0  # 結果是 -2.0

# 除法的運算結果始終是浮點數
10.0 / 3  # 結果是 3.3333333333333335

# 取模運算
7 % 3  # 結果是 1
# i % j 結果的符號與 j 相同, 這與 C 語言不同
-7 % 3  # 結果是 2

# 冪運算 (x**y, x的y次方)
2 ** 3  # 結果是 8

# 用括號來強制優先運算
1 + 3 * 2  # 結果是 7
(1 + 3) * 2  # 結果是 8

# 布林值是基本資料型別
True  # 值為 真(true)
False  # 值為 假(false)

# 使用 not 來進行非運算
not True  # 結果是 假
not False  # 結果是 真

# 布林值操作符:
# 提示, 'and' 和 'or' 是區分大小寫的
True and False  # 結果是 假
False or True  # 結果是 真

# True 和 False 事實上也等同於 1 和 0, 只不過是使用了不同的關鍵字
True + True  # 結果是 2
True * 8  # 結果是 8
False - 5  # 結果是 -5

# 比較運算子會檢查 True 和 False 的數字值
0 == False  # 結果是 真
1 == True  # 結果是 真
2 == True  # 結果是 假
-5 != False  # 結果是 真

# 對整數使用布林邏輯操作符, 則會將它們轉換為布林值以求值,但返回未轉換的值
# 不要把 bool(ints) 和 位運算 and/or 搞混了
bool(0)  # 返回 假
bool(4)  # 返回 真
bool(-6)  # 返回 真
0 and 2  # 返回 0
-5 or 0  # 返回 -5

# 運算子 '等同於' 是 ==
1 == 1  # 返回 真
2 == 1  # 返回 假

# 運算子 '不等於' 是 !=
1 != 1  # 返回 假
2 != 1  # 返回 真

# 更多比較運算子
1 < 10  # 返回 真
1 > 10  # 返回 假
2 <= 2  # 返回真
2 >= 2  # 返回真

# 檢查一個值是否在指定範圍內
1 < 2 and 2 < 3  # 返回 真
1 < 3 and 3 < 2  # 返回 假
# 連線起來, 這樣看起來會更好看些
1 < 2 < 3  # 返回 真
2 < 3 < 2  # 返回 假

# (is 與 ==) is 將會檢查兩個變數是否引用了同一個物件, 但是 == 檢查
# 兩個物件是否指向了相同的值
a = [1, 2, 3, 4]  # 使 a 指向一個新的列表, [1, 2, 3, 4]
b = a  # 使 b 指向 a 所指向的物件
b is a  # 返回 真, a 和 b 引用的是同一個物件
b == a  # 返回 真, a 和 b 的物件是相等的
b = [1, 2, 3, 4]  # 使 b 指向一個新的列表, [1, 2, 3, 4]
b is a  # 返回 假, a 與 b 並不引用同一個物件
b == a  # 返回 真, a 和 b 的物件使相等的

# 字串可以使用 雙引號 " 或 單引號 ' 來建立
"這是一個字串"
'這也是一個字串'

# 字串可以相加
"Hello " + "world!"  # 返回 "Hello world!"
"Hello " "world!"  # 等同於 "Hello world!"

# 字串可以用作一個字元列表
"Hello world!"[0]  # 返回 'H'

# 你可以獲取字串的長度:
len("這是一個字串")  # 返回 7

# 你可以使用 f-字串 或 格式化字串 來對字串文字進行格式化 (在 Python 3.6 或更高版本)
name = "小紅"
f"她說她的名字是{name}."  # 返回 "她說她的名字是小紅."
# 你可以基本的將 Python 表示式放到括號內, 然後它就會被輸出到字串中.
f"{name}是{len(name)}字元長."  # 返回 "小紅是兩個字元長"

# None 是一個物件
None  # 返回 None

# 不要使用等同於運算子 '==' 來比較物件和 None
# 使用 'is' 來代替. 這個會檢查物件標識是否相同.
"etc" is None  # 返回 假
None is None  # 返回 真

# None, 0, 以及空的字串/列表/字典/元組, 都計算為 假
bool(0)  # => 假
bool('')  # => 假
bool([])  # => 假
bool({})  # => 假
bool(())  # => 假

####################################################
## 2. 變數和集合
####################################################

# Python 有一個 print 函式, 用於標準輸出
print("我是Python, 很高興見到你!")  # => 我是Python, 很高興見到你!

# 預設的, print 函式還會在結尾輸出一個換行符
# 使用可選引數 'end' 來改變末尾的字串.
print("Hello, world", end="!")  # => Hello, world!   # 輸出後沒有換行

# 從控制檯獲取輸入資料的簡單方式:
input_string_var = input("Enter some data:")  # 以字串的形式返回輸入資料

# Python 中沒有變數的宣告, 只有賦值.
# 命名規範是使用小寫以及下劃線, 就像這樣: lower_case_with_underscores
some_var = 5
some_var  # => 5

# 訪問一個沒有生命的變數是錯誤的
# 檢視控制流來獲取更多異常捕捉資訊
some_unknown_var  # 這是一個一個未定義的變數, 執行時將丟擲 NameError 異常

# if 也可用作一個表示式
# 等同於 C 語言中的 '?:' 三元運算子
"yay" if 0 > 1 else "nay!"  # => "nay"

# 列表可以儲存一個序列, 可以這樣定義:
li = []
# 你可以為其指定初始值
other_li = [4, 5, 6]

# 使用 append 方法在列表的末尾新增一些什麼東西
li.append(1)  # 現在 li 的值是 [1]
li.append(2)  # 現在 li 的值是 [1, 2]
li.append(4)  # 現在 li 的值是 [1, 2, 4]
li.append(3)  # 現在 li 的值是 [1, 2, 4, 3]
# 使用 pop 方法從列表的末尾移除元素
li.pop()  # 返回 3, 並且現在 li 的值是 [1, 2, 4]
# 重新將它放回去
li.append(3)  # 現在 li 又變成 [1, 2, 4, 3] 了

# 像訪問陣列一樣訪問一個列表
li[0]  # => 1
# 訪問列表的最後一個元素
li[-1]  # => 3

# 如果索引超出界限, 那麼會丟擲 IndexError 異常
li[4]  # 丟擲 IndexError 異常

# 你可以使用切片語法來檢視一個範圍內的元素
# 起始索引包含在內, 但結束索引不包含在內
# (對於數學型別來講, 它是一個 閉/開 區間)
li[1:3]  # 返回從索引1到3的一個列表 => [2, 4]
li[2:]  # 返回從索引2開始的列表 => [4, 3]
li[:3]  # 返回從開始到索引3的列表 => [1, 2, 4]
li[::2]  # 返回一個每兩個元素選擇一個的列表 => [1, 4]
li[::-1]  # 返回反向排列的列表 => [3, 4, 2, 1]
# 使用任意組合來實現高階切片
# li[起始:結束:步長]

# 使用切片來建立一個單層的深度拷貝
li2 = li[:]

# 使用 "del" 來刪除任意元素
del li[2]  # 現在 li 的值是 [1, 2, 3]

# 刪除第一個匹配值的元素
li.remove(2)  # 現在 li 的值是 [1, 3]
li.remove(2)  # 丟擲 ValueError 異常, 2 並不在這個列表中

# 在指定索引處插入元素, 列表.insert(索引, 元素)
li.insert(1, 2)  # 現在 li 的值又是 [1, 2, 3] 了

# 獲取第一個匹配元素的索引
li.index(2)  # => 1
li.index(4)  # 丟擲 ValueError 異常, 4 不在這個列表中

# 你可以將列表相加
# 提示: values 和 other_li 的值不會被修改
li + other_li  # => [1, 2, 3, 4, 5, 6]

# 使用 "extend()" 連線列表, extend 的意思是擴充
li.extend(other_li)  # 現在 li 的值是 [1, 2, 3, 4, 5, 6]

# 使用 "in" 檢查元素是否存在於列表中
1 in li  # => True

# 使用 "len()" 檢查列表的長度
len(li)  # => 6

# 元組與列表相像, 但是不可更改
tup = (1, 2, 3)
tup[0]  # => 1
tup[0] = 3  # 丟擲一個 TypeError

# 提示, 長度為一的元組的最後一個元素必須有一個逗號跟隨, 但是
# 其他長度的元組, 儘管是0, 也不需要
type((1))  # => <class 'int'>
type((1,))  # => <class 'tuple'>
type(())  # => <class 'tuple'>

# 大多數的列表操作符都可以在元組上使用
len(tup)  # => 3
tup + (4, 5, 6)  # => (1, 2, 3, 4, 5, 6)
tup[:2]  # => (1, 2)
2 in tup  # => True

# 你可以將元組(或者列表)解包為變數
a, b, c = (1, 2, 3)  # 現在, a 是 1, b 是 2, c 是 3
# 你還可以使用擴充解包
a, *b, c = (1, 2, 3, 4)  # 現在, a 是 1, b 是 [2, 3], c 是 4
# 預設情況下, 即便你忽略括號, 也會建立一個元組
d, e, f = 4, 5, 6  # 元組 4, 5, 6 被解包為變數 d, e, f
# 變數值分別如下: d = 4, e = 5 以及 f = 6
# 那麼看看交換兩個值是多麼的簡單
e, d = d, e

# 字典用於儲存從鍵到值的對映
empty_dict = {}  # 建立了一個空字典
# 下面是一個帶有初始值的字典
filled_dict = {"one": 1, "two": 2, "three": 3}

# 提示: 字典的鍵必須是不可變的型別.  這是為了確保
# 鍵能夠轉換為用於快速查詢的常量雜湊值.
# 不可變型別包含整數, 浮點數, 字串, 元組
invalid_dict = {[1, 2, 3]: "123"}  # 丟擲 TypeError: unhashable type: 'list' 異常. (型別錯誤: 無法進行雜湊化的型別: '列表')
valid_dict = {(1, 2, 3): [1, 2, 3]}  # 然而, 值可以是任何型別

# 使用 [] 來查詢值
filled_dict["one"]  # => 1

# 使用 "keys()" 來獲取所有的鍵並作為一個可迭代物件返回. 我們需要在 list() 中將呼叫結果轉換
# 為一個列表. 這個稍後再講. 提示 - 在 Python 低於 3.7 的版本中
# 字典鍵索引的順序是無法保證的. 你的結果可能不與下面的例子完全相等. 然而, 在 Python 3.7 中, 字典
# 元素會保持它們被插入到字典的順序
list(filled_dict.keys())  # => ["three", "two", "one"] 在低於 3.7 的 Python 版本中
list(filled_dict.keys())  # => ["one", "two", "three"] 在 3.7 或更高的 Python 版本中

# 使用 "values()" 來獲取所有的值並作為可迭代物件返回. 同樣, 我們需要將其在
# list() 中轉換, 以取出這個可迭代物件的值. 提示 - 和上面鍵的順序是一樣的
list(filled_dict.values())  # => [3, 2, 1] 在低於 3.7 的 Python 版本中
list(filled_dict.values())  # => [1, 2, 3] 在 3.7 或更高的 Python 版本中

# 使用 "in" 來檢查鍵是否在字典中
"one" in filled_dict  # => True
1 in filled_dict  # => False

# 通過不存在的鍵來查詢字典, 會丟擲 KeyError 異常
filled_dict["four"]  # KeyError

# 使用 "get()" 方法來避免 KeyError 異常
filled_dict.get("one")  # => 1
filled_dict.get("four")  # => None
# 這個方法支援當找不到值時返回指定的預設值
filled_dict.get("one", 4)  # => 1
filled_dict.get("four", 4)  # => 4

# "setdefault()" 只有在給定鍵不存在的時候將值插入到字典
filled_dict.setdefault("five", 5)  # filled_dict["five"] 被設定為了 5
filled_dict.setdefault("five", 6)  # filled_dict["five"] 仍然是 5

# 向字典中新增內容
filled_dict.update({"four": 4})  # => {"one": 1, "two": 2, "three": 3, "four": 4}
filled_dict["four"] = 4  # 向字典中新增的另一種方式

# 自 Python 3.5 以來, 你還可以使用擴充解包選項
{'a': 1, **{'b': 2}}  # => {'a': 1, 'b': 2}
{'a': 1, **{'a': 2}}  # => {'a': 2}

# 集合用來儲存 ... 額, 集合
empty_set = set()  # 空集合
# 用一組值來初始化一個集合, 嗯, 看起來有點像字典, 抱歉
some_set = {1, 1, 2, 2, 3, 4}  # some_set 現在的值是 {1, 2, 3, 4}

# 與字典中的鍵相似, 集合的元素必須是不可變的
invalid_set = {[1], 1}  # => 丟擲一個 TypeError: unhashable type: 'list' (型別錯誤: 無法進行雜湊化的型別: '列表')
valid_set = {(1,), 1}

# 向集合中新增一個或多個條目
filled_set = some_set
filled_set.add(5)  # filled_set 現在是 {1, 2, 3, 4, 5}
# 集合是不包含重複元素的
filled_set.add(5)  # 還是與之前一樣 {1, 2, 3, 4, 5}

# 使用 & 取交集
other_set = {3, 4, 5, 6}
filled_set & other_set  # => {3, 4, 5}

# 使用 | 取並集
filled_set | other_set  # => {1, 2, 3, 4, 5, 6}

# 使用 - 取差集
{1, 2, 3, 4} - {2, 3, 5}  # => {1, 4}

# 使用 ^ 取對稱差集
{1, 2, 3, 4} - {2, 3, 5}  # => {1, 4, 5}

# 檢查左側的集合是否是右側集合的超集
{1, 2} >= {1, 2, 3}  # => False

# 檢查左側的集合是否是右側集合的子集
{1, 2} <= {1, 2, 3}  # => True

# 使用 in 來檢查是否存在於集合中
2 in filled_set  # => True
10 in filled_set  # => False

# 生成一個單層的深層副本
filled_set = some_set.copy()  # filled_set 是 {1, 2, 3, 4, 5}
filled_set is some_set  # => False

####################################################
## 3. 控制流和可迭代物件
####################################################

# 首先我們宣告一個變數
some_var = 5

# 這是一個 if 語句, 縮排在 Python 中非常重要
# 約定語法是使用四個空格, 而不是水平製表符(tab)
# 這個將會列印 "some_var 比 10 小"
if some_var > 10:
    print("some_var 比 10 大")
elif some_var < 10:  # 這個 elif 語句是可選的
    print("some_var 比 10 小")
else:  # 這個也是可選的
    print("some_var 與 10 相等")

"""
for 語句用來迴圈遍歷列表
將會列印:
    狗是哺乳動物
    貓是哺乳動物
    老鼠是哺乳動物
"""
for animal in ["狗", "貓", "老鼠"]:
    # 你可以使用 format() 來插入格式化字串
    print("{}是哺乳動物".format(animal))

"""
"range(數字)" 返回一個數字的可迭代物件
從0到給定數字
將會列印:
    0
    1
    2
    3
"""
for i in range(4):
    print(i)

"""
"range(較小的數, 較大的數)" 返回一個數字的可迭代物件
從較小的數字到較大的數字
將會列印:
    4
    5
    6
    7
"""
for i in range(4, 8):
    print(i)

"""
"range(較小的數, 較大的數, 步長)" 返回一個數字的可迭代物件
從較小的數到較大的數, 以步長未每次增長的值
如果步長沒有指定, 預設值則是 1
將會列印:
    4
    6
"""
for i in range(4, 8, 2):
    print(i)

"""
迴圈一個列表, 並且同時檢索列表中每一個條目的索引和值
將會列印:
    0 狗
    1 貓
    2 老鼠
"""
animals = ["狗", "貓", "老鼠"]
for i, value in enumerate(animals):
    print(i, value)

"""
while 迴圈將一直進行到條件不再滿足為止
將會列印:
    0
    1
    2
    3
"""
x = 0
while x < 4:
    print(x)
    x += 1  # x = x + 1 的簡寫

# 使用 try/except 語句塊來處理異常
try:
    # 使用 "raise" 來丟擲異常
    raise IndexError("這是一個索引錯誤")
except IndexError as e:
    pass  # pass 只是一個佔位符(不進行任何操作). 通常你需要在這裡對異常進行處理
except (TypeError, NameError):
    pass  # 如果需要, 你可以同時處理多個異常.
else:  # try/except 語句塊的可選語句. 必須在所有 except 語句塊的後面
    print("一切正常!")  # 僅在 try 語句內沒有任何異常時執行
finally:  # 在任何情況下都會執行
    print("我們可以在這裡進行資源清理")

# 你可以使用 with 語句代替 try/finally 來清理資源
with open("我的檔案.txt") as f:
    for line in f:
        print(line)

# 向檔案中寫入內容
contents = {"aa": 12, "bb": 21}
with open("我的檔案1.txt", "w+") as file:
    file.write(str(content))  # 向檔案中寫入字串

with open("我的檔案2.txt", "w+") as file:
    file.write(json.dumps(content))  # 向檔案中寫入一個物件

# 從檔案中讀取
with open("我的檔案1.txt", "r+") as file:
    contents = file.read()  # 從檔案中讀取一個字串
print(contents)
# 列印: {"aa", 12, "bb": 21}

with open("我的檔案2.txt", "r+") as file:
    contents = json.load(file)  # 從檔案中讀取一個json物件
print(contents)
# print: {"aa": 12, "bb": 21}


# Python 提供了一個叫做 Iterable(可迭代的) 的基本抽象
# 一個可迭代物件是一個可視為序列的物件
# range 函式返回的物件就是一個可迭代物件

filled_dict = {"one": 1, "two": 2, "three": 3}
our_iterable = filled_dict.keys()
print(our_iterable)  # => dict_keys(['one', 'two', 'three']). 這是一個實現了 Iterable 介面的物件

# 我們可以檢索它
for i in our_iterable:
    print(i)  # 列印 one, two, three

# 然而, 我們不可以通過索引來找到元素
our_iterable[1]  # 丟擲 TypeError 異常

# 一個可迭代物件即為能夠建立迭代器的物件
our_iterator = iter(our_iterable)

# 迭代器是一個能夠記住當前通過它迭代狀態的物件
# 我們可以通過 "next()" 來獲取下一個物件
next(our_iterator)  # => "one"

# 它會保持我們遍歷的狀態
next(our_iterator)  # => "two"
next(our_iterator)  # => "three"

# 在迭代器已經返回完所有的資料後, 將會丟擲 StopIteration 異常
next(our_iterator)  # 丟擲 StopIteration 異常

# 我們也可以檢索它, 事實上, "for" 語句就是隱式的執行了這個操作
our_iterator = iter(our_iterable)
for i in iterator:
    print(i)

# 你可以通過呼叫 list() 來獲取可迭代物件或迭代器的所有元素.
list(our_iterable)  # => 返回 ["one", "two", "three"]
list(our_iterator)  # => 返回 [] 因為迭代狀態已經被儲存下來


####################################################
## 4. 函式
####################################################

# 使用 "def" 來建立一個新的函式
def add(x, y):
    print("x 是 {} 以及 y is {}".format(x, y))
    return x + y


# 呼叫帶引數的函式
add(5, 6)  # => 輸出 "x 是 5 以及 y 是 6", 並返回 11

# 呼叫函式的另一種方式是帶有關鍵字引數
add(y=6, x=5)  # 關鍵字引數可以在任何順序下正常執行


# 你可以定義接受數量可變的位置引數的函式
def varargs(*args):
    return args


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


# 同樣, 你可以定義接受數量可變的關鍵字引數的函式
def keyword_args(**kwargs):
    return kwargs


# 來呼叫一下, 然後看看會發生什麼
keyword_args(big="foot", loch="ness")  # => {"big": "foot", "loch": "ness"}


# 只要你想, 你也可以同時使用它們兩個
def all_the_args(*args, **kwargs):
    print(args)
    print(kwargs)


"""
all_the_args(1, 2, a=3, b=4) 將會列印:
    (1, 2)
    {"a": 3, "b": 4}
"""

# 在呼叫函式時, 你可以做相反的 args/kwargs!
# 使用 * 來擴充元組, 以及使用 ** 來擴充 kwargs.
args = (1, 2, 3, 4)
kwargs = {"a": 3, "b": 4}
all_the_args(*args)  # 等同於 all_the_args(1, 2, 3, 4)
all_the_args(**kwargs)  # 等同於 all_the_args(a=3, b=4)
all_the_args(*args, **kwargs)  # 等同於 all_the_args(1, 2, 3, 4, a=3, b=4)


# 返回多個值(通過賦值元組)
def swap(x, y):
    return y, x  # 通過沒有括號的元組來返回多個值
    # (提示: 括號雖然沒有寫, 但是也可以新增上)


x = 1
y = 2
x, y = swap(x, y)  # => x = 2, y = 1
# (x, y) = swap(x,y)  # 同樣, 括號雖沒有寫, 但是也可以新增上

# 函式作用域
x = 5


def set_x(num):
    # 區域性變數 x 與全域性變數 x 是不同的
    x = num  # => 43
    print(x)  # => 43


def set_global_x(num):
    global x
    print(x)  # => 5
    x = num  # => 全域性變數 x 現在被設定為 6 了
    print(x)  # => 6


set_x(43)
set_global_x(6)


# Python 支援本地函式
def create_adder(x):
    def adder(y):
        return x + y

    return adder


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

# 也支援匿名函式
(lambda x: x > 2)(3)  # => True
(lambda x, y: x ** 2 + y ** 2)(2, 1)  # => 5

# 下面是一些內建的高階函式
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]

# 你可以使用列表推導式來實現優秀的對映與過濾
# 列表推導式儲存可巢狀的列表以輸出
[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]

# 同樣, 你可以構建集合和字典推導式
{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}

####################################################
## 5. 模組
####################################################

# 匯入模組, 呼叫模組中的函式
import math

print(math.sqrt(16))  # => 返回4.0

# 你可以從模組中獲取指定的函式
from math import ceil, floor

print(ceil(3.7))  # => 4.0
print(floor(4.7))  # => 3.0

# 你可以從模組中匯入所有函式
# 警告: 不推薦這麼做
from math import *

# 你可以在匯入模組時, 為其起一個別名
import math as m

math.sqrt(16) == m.sqrt(16)  # => True

# Python 模組只是普通的 Python 檔案. 你
# 可以編寫你自己的模組, 然後匯入它們. 模組
# 的名字與檔名是相同的

# 你可以查詢模組內定義的函式和屬性
import math

dir(math)


# 如果有一個名為 math.py 的 Python 指令碼與
# 當前指令碼在同一個目錄下, 那麼這個檔案將會
# 代替 Python 內建模組以載入.
# 這是因為本地目錄有比 Python 的內建庫更高
# 的優先順序.


####################################################
## 6. 類
####################################################

# 使用 "class" 語句來建立一個類
class Human:
    # 類的屬性. 將在類的所有例項中共享
    species = "SlimeNull"

    # 基本的初始化器, 這個將會在類例項化時呼叫
    # 提示, 雙前置和後置下劃線表示 Python 使用
    # 的物件或屬性, 但是它們存在於使用者控制的命名
    # 空間. 方法(或者是物件, 屬性) 像 __init__, __str__,
    # __repr__ 等等, 被稱為特殊方法(有時也成為魔法方法)
    # 你不應該建立你自己的, 與它們重名的成員
    def __init__(self, name):
        # 將引數賦值給例項的 name 屬性
        self.name = name

        # 初始化屬性
        self._age = 0

    # 例項的方法. 所有的方法都需要使用 "self" 作為第一個引數
    def say(self, msg):
        print("{name}: {message}".format(name=self.name, message=msg))

        # 例項的另一個方法
        def sing(self):
            return '喲... 喲... 麥克風檢查... one... one two...'

    # 類的方法, 所有的例項都能夠訪問
    # 它們被呼叫時, 呼叫它的類將作為呼叫的第一個引數
    @classmethod
    def get_species(cls):
        return cls.species;

    # 靜態方法在呼叫時, 不會有類或例項的引用傳入
    @staticmethod
    def grunt():
        return "*咕嚕咕嚕*"

    # 屬性就像一個獲取器(getter)
    # 它將這個方法 age() 作為一個同名的, 只讀的屬性返回
    # 但是, 在 Python 中不需要寫繁瑣的獲取器和設定器(setter)
    @property
    def age(self):
        return self._age

    # 這個將允許屬性被設定
    @age.setter
    def age(self, age):
        self._age = age

    # 這個將允許屬性被刪除
    @age.deleter
    def age(self):
        del self._age


# 當 Python 直譯器讀取原始檔並執行時
# __name__ 會確保這個程式碼塊在模組中
# 是作為主程式執行的
if __name__ == '__main__':
    # 例項化類
    i = Human(name="小明")
    i.say("嗨~")
    j = Human("小紅")
    j.say("你好哇~")
    # i 和 j 是型別 Human 的例項, 換句話說, 他們都是 Human 物件

    # 呼叫類的方法
    i.say(i.get_species())          # => "小明: SlimeNull"
    # 更改共享屬性
    Human.species = "Little Wu♂ Fairy"
    i.say(i.get_species())          # => "小明: Little Wu♂ Fairy"
    j.say(j.get_species())          # => "小紅: Little Wu♂ Fairy"

    # 呼叫靜態方法
    print(Human.grunt())            # => "*咕嚕咕嚕*"

    # 靜態方法也可以通過例項來呼叫
    print(i.grunt())                # => "*咕嚕咕嚕*"

    # 為這個例項更新屬性
    i.age = 42
    # 獲取屬性
    i.say(i.age)                    # => "小明: 42"
    j.say(j.age)                    # => "小紅: 0"
    # 刪除屬性
    del i.age
    # i.age                         # => 將會丟擲 AttributeError 異常


####################################################
## 6.1 繼承
####################################################

# 繼承允許定義的新的子類從父類繼承
# 方法與變數

# 使用上面定義的 Human 類作為基類(父類), 我們可以定義
# 一個子類, Superhero, 它將繼承 Human 類的變數, 例如
# "species", "name", 以及 "age", 方法也是如此, 例如
# "sing" 與 "grunt". 但同時它也可以有自己的特殊屬性.

# 你可以將上面的類儲存到它們自己的檔案中來採用模組化檔案的優點
# 命名為, human.py

# 從別的檔案中匯入函式, 需要使用下面的格式
# from "沒有字尾的檔名" import "函式或者類"

from human import Human


# 在型別定義處以引數的形式指定父類
class Superhero(Human):

    # 如果字類僅僅是從父類繼承所有定義, 且沒有
    # 任何更改, 你可以只在這裡寫一個 "pass" 關鍵字 (別的不需要寫)
    # 但在我們這種情況下, pass 就要被註釋掉以為 Superhero 類建立
    # 它特有的內容
    # pass

    # 字類可以覆蓋父類的屬性
    species = "Superhuman"

    # 字類會自動的從父類繼承建構函式, 包括
    # 它的引數, 但是也可以定義另外的引數, 或者定義
    # 然後覆蓋它的方法, 例如這個類的建構函式
    # 這個建構函式從 Human 類繼承了 "name" 引數並且
    # 新增了 "superpower" 和 "movie" 引數
    def __init__(self, name, movie=False,
                 superpowers = ["力大無窮", "金剛不壞之身"]):

        # 新增額外的型別屬性
        self.fictional = True
        self.movie = movie
        # 注意可變的預設值, 因為它們預設是共享的
        self.superpowers = superpowers

        # "super" 函式使你可以訪問被字類重寫了的
        # 父類的方法, 在這裡, 我們要使用 "__init__"
        # 這將會呼叫父類的建構函式
        super().__init__(name)

    # 覆蓋 sing 方法
    def sing(self):
        return "Dun, dun, DUN!"

    # 新增附加的例項方法
    def boast(self):
        for power in self.superpowers:
            print("我有{pow}的能力!".format(pow=power))


if __name__ == '__main__':
    sup = Superhero(name="蒂克")

    # 例項型別檢查
    if isinstance(sup, Human):
        print("我是人類")
    if type(sup) is Superhero:
        print("我是一個超級英雄")

    # 通過使用 getattr() 和 super() 來獲取方法解析搜尋順序
    # 這個屬性是動態的, 並且可以被更新
    print(Superhero.__mro__)    # => (<class '__main__.Superhero'>,
                                # => <class 'human.Human'>, <class 'object'>)

    # 通過它自己的型別屬性來呼叫父類的方法
    print(sup.get_species())    # => Superhuman

    # 呼叫被重寫了的方法
    print(sup.sing())           # => Dun, dun, DUN!

    # 呼叫 Human 的方法
    sup.say("勺子?")             # => 蒂克: 勺子?

    # 呼叫僅存在於 Superhero 中的方法
    sup.boast()                 # => 我有力大無窮的能力!
                                # => 我有金剛不壞之身的能力!

    # 繼承的型別屬性
    sup.age = 32
    print(sup.age)              # => 32

    # 僅在 Superhero 中存在的屬性
    print('我能獲得奧斯卡獎嗎?' + str(sup.movie))

####################################################
## 6.2 多重繼承
####################################################

# 另一個類的定義
# bat.py
class Bat:

    species = '貝蒂'

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

    # 這個類也有一個說的方法
    def say(self, msg):
        msg = '... ... ...'
        return msg

    # 並且它也有自己的方法
    def sonar(self):
        return '))) ... ((('

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


# 然後現在另一個型別定義繼承自 Superhero 和 Bat
# superhero.py
from superhero import Superhero
from bat import Bat

# 定義 Batman 並同時繼承 Superhero 和 Bat
class Batman(Superhero, Bat):

    def __init__(self, *args, **kwargs):
        # 繼承屬性通常需要 super
        # super(Batman, self).__init__(*args, **kwargs)
        # 然而在這裡處理多繼承, 所以 super()
        # 僅僅適用於 MRO 列表的下一個基類
        # 所以, 我們需要為所有父類顯式的呼叫 __init__
        # *args 和 **kwargs 允許使用非常簡潔的方式傳遞所有引數,
        # 每一個父類都 "給洋蔥剝一層皮"
        Superhero.__init__(self, 'anonymous', movie=True,
                           superpowers=['Wealthy'], *args, **kwargs)
        Bat.__init__(self, *args, can_fly=False, **kwargs)
        # 覆蓋相同名稱的屬性的值
        self.name = "悲傷的阿弗萊克"

    def sing(self):
        return "吶 吶 吶 吶 吶 蝙蝠俠!"


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

    # 通過使用 getattr() 和 super() 來獲取方法解析搜尋順序
    # 這個屬性是動態的, 並且可以被更新
    print(Batman.__mro__)       # => (<class '__main__.Batman'>,
                                # => <class 'superhero.Superhero'>,
                                # => <class 'human.Human'>,
                                # => <class 'bat.Bat'>, <class 'object'>)

    # 通過它自己的型別屬性來呼叫父類的方法
    print(sup.get_species())    # => Superhuman

    # 呼叫被重寫了的方法
    print(sup.sing())           # => 吶 吶 吶 吶 吶 蝙蝠俠!

    # 呼叫 Human 的方法, 原因是繼承問題
    sup.say('我同意')            # => 悲傷的阿弗萊克: 我同意

    # 呼叫存在於第二個祖先的方法
    print(sup.sonar())          # => ))) ... (((

    # 繼承了的型別屬性
    sup.age = 100
    print(sup.age)              # => 100

    # 從第二個祖先繼承的預設值被重寫了的屬性
    print('我能飛嗎?' + str(sup.fly))  # => 我能飛嗎? 不能



####################################################
## 7. 高階
####################################################

# 生成器可以幫助你寫一些更簡便的程式碼 (偷懶)
def double_numbers(iterable):
    for i in iterable:
        yield i + i

# 生成器是 高效記憶體 的, 因為它們只載入需要的資料
# 處理可迭代物件的下一個值, 這使他們可以在非常大
# 的值域上執行操作.
# 提示: `range` 在 Python3 中取代了 `xrange`
for i in double(range(1, 900000000)):  # `range` 是一個生成器
    print(i)
    if (i > 30):
        break

# 就像你能建立列表推導式一樣, 你也可以建立
# 生成器推導式
values = (-x for x in [1,2,3,4,5])
for x in values:
    print(x)  # 列印 -1 -2 -3 -4 -5 到 控制檯/終端

# 你也可以將生成器推導式轉換為一個列表
values = (-x for x in [1,2,3,4,5])
gen_to_list = list(values)
print(gen_to_list)  # => [-1, -2, -3, -4, -5]

# 裝飾器
# 在這個例子中, `beg` 與 `say` 交換. 如果 say_please 是 True, 那麼它
# 將會改變返回的訊息
from functools import wraps


def beg(target_function):
    @wraps(target_function)
    def wrapper(*args, **kwargs):
        msg, say_please = target_function(*args, **kwargs)
        if say_please:
            return "{} {}".format(msg, "求你了, 我很窮 :(")
        return msg

    return wrapper


@beg
def say(say_please=False):
    msg = "能為我買瓶啤酒嗎?"
    return msg, say_please


print(say())                 # 能為我買瓶啤酒嗎?
print(say(say_please=True))  # 能為我買瓶啤酒嗎? 求你了, 我很窮 :(

相關文章