一文了解Python錯誤、異常和文件讀寫

本文將主要介紹 Python 的語法錯誤、異常、文件的讀取等基礎知識。閱讀本文預計需要 15 min

1. 前言

錯誤和異常,以及讀取文件,寫入文件都是我們經常會遇到的。本文主要內容:

  • 語法錯誤
  • 異常定義和查看報錯信息
  • 異常的處理
  • 拋出異常
  • 自定義異常
  • 文件的讀取
  • 讀取大文件的方法

2. 語法錯誤

在 Python 中主要分爲兩種錯誤:語法錯誤(syntax errors) 和異常(exceptions)。
語法錯誤,又稱解析錯誤(parsing errors),指代碼的語法不符合 Python 語法規則,導致“翻譯官” Python 解釋器無法翻譯你的代碼給計算機,導致會報錯。這很常見,尤其是我們初學編程的時候。

語法錯誤是代碼執行前檢測到的錯誤

舉個栗子:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

上面就是一個語法錯誤,學習如何看報錯信息。
首先我們看最後一行,SyntaxError: invalid syntax 這句就是告訴我們寫的代碼出現了 SyntaxError 即語法錯誤。

繼續往上面看,發現 Python 解釋器輸出了出現語法錯誤的一行,並且在這句代碼有問題的地方(也有可能是這附近,只是在這裏檢測出來了)用一個箭頭 ^ 指示,同時再上面一行輸出了文件名 "<stdin>" 和出問題代碼所在的行數(line),這裏是第一行代碼出問題了,而且是在 print()函數附近。

通過這樣的順序,我們就可以快速的定位到錯誤代碼的位置,並且知道是什麼錯誤,從而進行修改。這句代碼主要是因爲 print()函數前面少了一個冒號(:)。

學會看報錯信息,定位錯誤,對於調試代碼非常重要!語法錯誤相對比較簡單,就做這些總結。

3. 異常

除了語法錯誤,我們更多遇見的是異常(exception)。

3.1 異常的定義

有時候,我們的語法沒有任何問題,但是在代碼執行的時候,還是可能發生錯誤。這種在代碼運行時檢測到的錯誤成爲異常

下面展示一些常見的異常(來源於 Python 官網):

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

我們對比一下語法錯誤和異常的報錯信息,以 10 * (1/0) 的報錯信息爲例:

  • 異常的最後一行也是告訴我們程序執行過程中遇到了什麼錯誤,異常的類型是什麼,這裏是 ZeroDivisionError即分母爲 0 的異常。
  • 我們在看第一行 Traceback (most recent call last): 這句是告訴我們程序在執行過程中追蹤到一個錯誤,下面開始一步一步追蹤定位錯誤。這在語法錯誤裏面是沒有的。因爲語法錯誤是在程序執行前檢測到的。官網的說法是以堆棧回溯的形式顯示發生異常時的上下文,通常它包含列出源代碼行的堆棧回溯,但是不會顯示從標準輸入中讀取的行。
  • 接着一行 File "<stdin>", line 1, in <module> 是告訴我們,文件 "<stdin>" 模塊中的第 1 行代碼出問題了。從而我們就定位到了代碼出錯的位置。可以發現,這裏沒有出現語法錯誤的箭頭。

有時候 Traceback 很長,這時候我們可以先看最後一行,知道錯誤類型,然後從上往下看報錯信息,最終定位到出問題代碼的位置(在哪個文件,多少行代碼),從而修改代碼。

異常的類型很多,這裏依次列出了 ZeroDivisionError, NameError 和 TypeError 三種異常,這些異常名稱是內置的標識符(identifiers),不是關鍵字(keywords)。

更多的內置異常(Built-in Exceptions)可以參看官網Built-in Exceptions獲取它們的介紹和意義。

3.2 異常的處理

Python 中可以用 try...except 語句來處理異常。這有點像 if...else 條件分支語句。

看這個例子:

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")

這段代碼是要求用戶一直輸入,直到輸入一個有效的整數,但允許用戶通過 Ctrl + C 中斷程序,這時引發的是 KeyboardIterrut 異常。
下面說明一下 try 語句的工作原理:

  1. 首先執行 try 子句(即 try 和 except 之間的所有語句).
  2. 如果沒有異常發生,則跳過 except 子句,從而結束 try 語句的執行。
  3. 如果執行 try 語句時發生了異常,則跳過 try 子句剩餘的部分。然後,如果異常的類型和 except 關鍵字後面的異常匹配,則執行相應的 except 子句,然後繼續執行 except 後面的代碼。
  4. 如果發生的異常和 except 子句中指定的異常不匹配,則將其傳遞到外部的 try 語句中;如果沒有找到處理程序,則他是一個未處理異常,程序將停止並顯示相應的信息,如果什麼處理都沒有,就什麼都不顯示。

如下面這個,except 中什麼都不處理,所以程序直接結束,什麼都沒有輸出:

>>> try:
...     x = int(input("Please enter a number: "))
...     print("good!")
... except ValueError:
...     # print("Oops!  That was no valid number.  Try again...")
...     pass
...
Please enter a number: a
>>>

一個 try 語句可以有多個 except 子句,以指定不同異常的處理程序,但是最多會執行一個 except 子句,其他的都會跳過。還有一點要注意,except 子句只會處理 try 子句中發生的異常,對於 except 子句中發生的異常是沒有不會被 except 子句處理的。換句話說,就是異常處理程序(except 子句)自身發生異常,try 語句中的 except 都無法處理,交給更高一級處理。如:

>>> try:
...     x = int(input("Please enter a number: "))
...     print("good!")
... except ValueError:
...     # print("Oops!  That was no valid number.  Try again...")
...     print(a)
...
Please enter a number: b
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'b'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
NameError: name 'a' is not defined
>>>

這裏可以發現有兩個異常,一個是 try 子句引發的 ValueError,還有一個異常處理程序 except 子句中引發的 NameError,這是 try 語句之外的更高一級處理的結果(Python 解釋器)。

一個子句也可以將多個異常命名爲元組,如:

... except (RuntimeError, TypeError, NameError):
...     pass

注意,Python 的錯誤也是 class,所有錯誤類型都繼承自 BaseException,所以使用 except 時一定要注意,它不但能捕獲該類型的錯誤,還能把其子類也“一網打盡”,如:

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except B:
        print("B")
    except D:
        print("D")
    except C:
        print("C")


輸出結果:
B
B
B

簡單說,就是 B 是 Exception 的子類,C 是 B 的子類,D 是 C 的子類,所以 raise 拋出錯誤的時候(raise 待會會說),B、C、B 三種異常類型都可以被 第一個 except B 子句捕獲,這樣就大致我們後面的 except 子句永遠不會生效。所以要注意書寫順序,先子類,再父類,異常的繼承順序可以看前面給的鏈接。這裏正確的做法是把,except B 作爲最後一個 except 子句,這樣輸出結果就是 B、C、D。

try…except 還有一個好處就是可以跨越多層調用,即不僅可以處理 try 子句遇到的異常,還可以處理 try 子句中調用函數發生的異常,如:main()調用 foo(),foo()調用 bar(),結果 bar()出錯了,這時只要 main()捕獲了,就可以處理:

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        print('Error:', e)

main()

輸出結果:
Error: division by zero

即我們不需要在每個可能出錯的地方去捕獲錯誤,只需要在合適的層次去捕獲錯誤就可以了,這樣可以減少寫 try…except… 的麻煩。

最後的 except 子句可以省略異常名,來作爲通配符(匹配剩餘所有的異常),但是這種做法要慎重,因爲這種做法很容易掩蓋真正的編程錯誤,讓你不知道到底發生了什麼異常。此外它也還可以用於打印錯誤消息,然後重新引發異常(這個很常用,也很有用),如:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

try…except 語句還有一個可選的 else 子句,在使用時,else 子句必須放在所有的 try 子句後面,如果 try 子句沒有引發異常,就會執行,else 子句。else 子句常用在需要向 try 子句添加額外代碼的時候。對官網這部分的描述還不是特別理解,這裏放一個官網的例子:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

except 子句可以在異常名稱後指定一個變量,用於綁定一個異常實例,它的參數存儲在 instance.args 中,通常出於方便考慮,異常實例會定義 __str__() 特殊方法,因此可以直接打印參數,而不需要用引用的形式.arg,同時也可以在拋出之前首先實例化異常,並根據需要向其添加任何屬性:

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))  # the exception instance
    print(inst.args)  # arguments stored in .args
    print(inst)  # __str__ allows args to be printed directly,
    # but may be overridden in exception subclasses
    x, y = inst.args  # unpack args
    print('x =', x)
    print('y =', y)

輸出結果:
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

3.3 拋出異常

有時遇到異常,我們不知道怎麼處理,我們可以把異常拋出去,交給上面處理,Python 中 raise 語句允許程序員強制發生指定的異常,如:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

raise 語句唯一的參數就是要拋出的異常實例,這個實例儘量使用 Python 內置的異常類型,如果傳遞的是一個異常類,它將通過調用沒有參數的構造函數來隱式實例化:raise ValueError # 等價於 'raise ValueError()'

raise 語句不帶參數會把當前錯誤原樣拋出。

此外,在 except 中 raise 一個 Exception,還可以把一種類型的異常轉化爲另一種類型,但儘量別這麼幹。

3.4 用戶自定義異常

用戶可以自定義異常,但是自定義異常時需要注意:

  1. 自定義的異常通常應該直接或間接從 Exception 類派生。
  2. 自定義的異常通常保持簡單,只提供許多屬性,這些屬性允許處理程序爲異常提取有關錯誤的信息。
  3. 在創建可能引發多個不同錯誤的模塊時,通常的做法是爲該模塊定義的異常創建基類,併爲不同的錯誤創建特定異常類的子類。如:
  4. 大多數異常名字都以 Error 結尾,類似於標準異常的命名。
class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

3.5 定義清理操作

try 語句還有一個 finally 子句是可選的,用於處理在所有情況下都必須執行的清理操作。finally 子句將作爲 try 語句的最後一項任務被執行,無論 try 子句是否發生異常,均會執行,如:

try:
    print(a)
except NameError as e:
    print('NameError: ', e)
finally:
    print('finally...')

結果輸出:
NameError:  name 'a' is not defined
finally...

這裏有一些 finally 的細節,自己以前沒有注意到,看官方文檔後發現挺重要的:

  1. 如果在執行 try 子句期間發生了異常,該異常可由一個 except 子句進行捕獲處理。 如果異常沒有被某個 except 子句所處理,則該異常會在 finally 子句執行之後被重新引發。
  2. 異常也可能在 except 或 else 子句執行期間發生。 同樣地,該異常會在 finally 子句執行之後被重新引發。
  3. 如果在執行 try 語句時遇到一個 break, continue 或 return 語句,則 finally 子句將在執行 break, continue 或 return 語句之前被執行。
  4. 如果 finally 子句中包含一個 return 語句,則返回值將來自 finally 子句的某個 return 語句的返回值,而非來自 try 子句的 return 語句的返回值。

以上這些都是強調了 finally 子句一定會被執行,同時它的執行順序優先於 try 語句無法處理的異常,也優先於 break、return、continue 等控制語句。
看兩個官網給的例子:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

一個更復雜的例子:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)  # try 子句沒有異常,else子句執行,最後是finally
result is 2.0
executing finally clause
>>> divide(2, 0)  # 異常被except 捕獲,正確處理,finally 在其之後執行
division by zero!
executing finally clause
>>> divide("2", "1")  # 這裏except子句無法處理該類異常,所以先執行finally子句,再重新引發異常
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

在實際應用開發中,finally 子句非常有用,它常用於釋放外部資源,如文件或者網絡連接等。因爲及時使用不成功,也可以成功釋放這些資源。

3.5 小結

最後列一下 try...except...else...finally 都出現的格式:

try:
    codeblock
except:
    codeblock
else:
    codeblock
finally:
    codeblock
  1. try 語句可以很好地處理一些異常,但是不要濫用,只在關鍵的位置使用。這個需要多積累經驗,看看牛人都是怎麼用的。
  2. except 子句可以有多個,但要注意父類和子類異常類型的順序。
  3. 不要輕易使用 except 後不加異常名稱,這會導致異常的類型無法確定,無法更好的定位異常。
  4. 要額外添加到 try 子句的代碼最好放到 else 子句中,else 子句是可選的,要使用 else,則 except 子句必須要有。這個需要再看看牛人怎麼用,借鑑用法。
  5. finally 子句是可選的,無論如何都會執行,但是要注意如果出現了 try 語句無法處理的異常時,會先執行 finally 子句,再重新引發異常。
  6. 如果在執行 try 語句時遇到一個 break, continue 或 return 語句,則先執行 finally 子句,再執行 break, continue 或 return 語句。
  7. 如果 finally 子句中包含一個 return 語句,則返回值將來自 finally 子句的某個 return 語句的返回值,而非來自 try 子句的 return 語句的返回值。
  8. finally 子句在使用文件、網絡連接等資源時非常有用,可以保證我們成功釋放資源。

4. 文件

讀寫文件是最常見的 IO 操作,在磁盤上讀寫文件的功能是由操作系統提供的,所以讀寫文件就是請求操作系統打開一個文件對象(file object),這通常描述爲文件描述符,然後通過操作系統提供的接口從這個文件對象中讀取數據,或者寫入數數據。有點像,我們告訴操作系統我們要做什麼,操作系統再去幫我們完成。

4.1 文件的讀寫

Python 中 open() 函數用來讀寫文件,用法和 C 是兼容的。它返回的是一個文件對象(file object)。open()函數有好幾個參數,最常用的是 open(filename, mode=‘r’, encoding=None, errors=None)。

  • filename 代表文件的名字,需要我們傳入一個包含文件名的字符串,這是必傳參數。
  • mode 也是需要我們傳入一個字符串,告訴函數,以什麼樣的方式打開文件,默認情況下是文件只能讀取('r')。還有其他幾種方式,待會列出來。
  • encoding 是使用文件時的編碼或解碼的格式,這隻用於文本文件,不能用於二進制文件,默認模式和平臺有關,因此有時讀文本文件,我們會指定 encoding='utf-8'
  • errors 是用於編碼錯誤的,如果一個文件存在多種編碼,這個時候,我們可以指定 errors=‘ignor’ 來忽略錯誤,這會造成部分數據丟失。

open() 函數默認是打開文本文件(text file),打開的方式主要有一些幾種:

mode 描述
‘r’ 默認情況,只讀模式
‘w’ 只寫模式,注意存在的同名文件會被刪除
‘a’ 追加寫模式,任何寫入的數據都會自動添加到文件末尾
‘x’ 只寫模式,推薦,存在同名文件會提示報錯
‘+’ 上面的幾種模式加上’+’,都變成可讀可寫,如:r+,w+,a+等

默認打開的是文本文件,如果我們要打開圖片、視頻等二進制文件時,就需要用二進制模式,模式和上面類似,只是加一個 ‘b’ 就可以了。

mode 描述
‘rb’ 默認情況,只讀模式
‘wb’ 只寫模式,注意存在的同名文件會被刪除
‘ab’ 追加寫模式,任何寫入的數據都會自動添加到文件末尾
‘xb’ 只寫模式,推薦,存在同名文件會提示報錯
‘+’ 上面的幾種模式加上’+’,都變成可讀可寫,如:rb+,wb+,ab+等

舉個栗子:

# 我在代碼當前目錄建了一個b.txt文件
f = open('b.txt', mode='r')  # 打開文件
for line in f.readlines():
    print(line, end='')
f.close()  # 關閉文件

輸出結果:
Java
Python
C
C++

如果文件 b.txt 不存在,會得到一個 FileNotFoundError 異常,文件對象的方法,我們待會講。
注意,我們打開文件後,一定要記得關閉,不然文件對象會一直佔用操作系統資源。所以這裏使用 try…finally 來完成是非常好的。

try:
    f = open('b.txt', mode='r')
    for line in f.readlines():
        print(line, end='')
finally:
    if f:
        f.close()

這樣寫很繁瑣,Python 提供了更加優雅的方式幫助我們打開文件,會在我們使用結束文件,或者處理文件時發生異常,都能自動關閉文件,這也是 Python 官方推薦的方式。

with open('b.txt', mode='r') as f:
    for line in f.readlines():
        print(line, end='')

輸出結果:
Java
Python
C
C++

發現 with 關鍵字的寫法更加優雅,簡潔,極其推薦。

接下來將簡單介紹一些文件對象的方法。

4.2 文件對象的方法

Python 內置了很多文件對象的方法幫助我們對文件進行操作。下面列舉一些常用的方法,更多的方法,大家可以使用 help(file object)進行查看,如 help(f),我們就可以看到我們可以對文件對象 f,進行哪些操作。

  • close()用於關閉文件。使用了 with 這個就可以忽略了。

  • read(self, size=-1, /),用於按大小讀取文件的大小,如果不傳入 size 這個僅限位置參數,則默認讀取整個文件。

  • readline(self, size=-1, /),用於按行讀取文件,每次讀取文件的一行。

  • readlines(self, hint=-1, /),用於按行讀取文件,hint 僅限位置參數用於指定要讀取的行數,如果不指定,默認讀取文件的全部行數,並放入一個列表中返回。

  • write(self, text, /),寫入文本,並返回寫入的文本字符數,即文本的長度。

  • writelines(self, lines, /),寫入文本,不過是寫入多行文本,需要我們傳入一個列表。不過需要注意,每一行行末的換行符\n需要我們自己添加。

  • seek(),指定文件指針在文件中的位置,seek(0) 代表將文件指針指向文件開頭,seek(n)代表將文件指針指向第 n 個字符。

  • tell(),告訴我們當前文件指針的位置。

下面我們就測試一下:
我們先在當前目錄新建一個 d.txt 文件裏面內容如下:

C
Python
Java

測試如下:

>>> f = open('d.txt', mode='r+')  # 讀寫方式打開d.txt
>>> f.tell()  # 告訴我當前文件指針位置在0,即開頭
0
>>> f.readline()  # 讀取返回文件的一行
'C\n'
>>> f.readline()  # 讀取返回文件的下一行
'Python\n'
>>> f.tell()  # 告訴我當前文件指針位置在11,即讀到了11個字符位置
11
>>> f.seek(0)  # 將文件指針移到開頭
0
>>> f.tell()  # 可以看到文件指針回到了開頭位置
0
>>> f.readlines(2)  # readlines()讀取兩行,返回一個列表
['C\n', 'Python\n']
>>> f.readlines()  # 讀取剩餘文件的全部行,返回一個列表
['Java']
>>> f.readline()  # 文件讀完了,繼續讀會得到一個空字符串
''
>>> f.tell()  # 文件指針在 15
15
>>> f.seek(0)  # 文件指針置於文件開始處
0
>>> f.read(11)  # read(11)指定讀取文件11個字符
'C\nPython\nJa'
>>> f.read()  # 未指定參數,讀取文件剩餘的全部內容
'va'
>>> f.seek(0)  # 將文件指針移到開頭
0
>>> f.read() # 一次性讀取全部文件內容到內存
'C\nPython\nJava'
>>> f.close()  # 關閉文件

好了,看明白上面的,我們再簡單看一個例子:

lines = ['C\n', 'Python\n', 'Java\n']
with open('d.txt', mode='w+') as f:
    f.writelines(lines)  # 寫入多行文件
    f.seek(0)  # 文件指針置於開頭,從頭開始讀取
    for line in f:  # 一行一行輸出文件
        print(line, end='')

結果輸出:
C
Python
Java

理解每一個方法,恰當的使用它們,可以讓我們的代碼更加高效。

4.3 大文件的讀取

文件比較小,我們內存夠大,所以方法選擇上沒那麼重要,但是如果我們的文件非常大, 比如 8 個 G,這時如果你用 f.read()去讀取,或者用 f.readlines(),默認讀取整個文件,那麼很可能你會因爲內存不足,系統崩潰。
所以怎麼讀取大文件呢?

其實解決思路很簡單,就是我們不要一次性都讀入,把文件拆分,比如一行一行讀入,或者一次讀入 1024 個字符。

方法一(官網推薦):

with open('d.txt') as f:
    for line in f:
        print(line, end='')

輸出結果:
C
Python
Java

這種情況適合一行一行讀入,但是如果一行很大,通常視頻圖片時,比如一行有 1G 呢,這時候可以指定大小讀入:

with open('d.txt') as f:
    while True:
        part = f.read(4)  # 每次讀取4個字符
        if part:
            print(part, end='')
        else:
            break

輸出結果:
C
Python
Java

這裏我們可以封裝成一個函數,方便我們更加靈活的調用和配置:

def read_part(file_path, size=1024, encoding="utf-8"):
    with open(file_path, mode='r', encoding=encoding) as f:
        while True:
            part = f.read(size)
            if part:
                yield part
            else:
                return None

file_path = r'd.txt'  # r 代表原始字符串
size = 2 # 每次讀取指定大小的內容到內存
encoding = 'utf-8'

for part in read_part(file_path=file_path, size=size, encoding=encoding):
    print(part, end='')

按大小讀取可以靈活控制一次讀取的 size,在速度上較按行讀取有優勢,適用於一些大的二進制文件,比如讀取一些大的視頻或者圖片等。
按行讀取在處理一些文本的時候感覺更加便利,按行讀更容易對文本進行處理。

當然我們也可以每次讀入指定的行數,這裏就不實現了。

到這裏,我們基本把 Python 的基礎部分總結完了。

5. 巨人的肩膀

  1. Errors and Exceptions
  2. Reading and Writing Files

推薦閱讀:

  1. 編程小白安裝Python開發環境及PyCharm的基本用法
  2. 一文了解Python基礎知識
  3. 一文了解Python數據結構
  4. 一文了解Python流程控制
  5. 一文了解Python函數
  6. 一文了解Python部分高級特性
  7. 一文了解Python的模塊和包
  8. 一文了解 Python 中的命名空間和作用域
  9. 一文了解Python面向對象
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章