成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

異常是怎么實(shí)現(xiàn)的?虛擬機(jī)是如何將異常拋出去的?

開發(fā) 前端
程序在運(yùn)行的過程中,總是會(huì)不可避免地產(chǎn)生異常,此時(shí)為了讓程序不中斷,必須要將異常捕獲掉。如果能提前得知可能會(huì)發(fā)生哪些異常,建議使用精確捕獲,如果不知道會(huì)發(fā)生哪些異常,則使用 Exception 兜底。

楔子

程序在運(yùn)行的過程中,總是會(huì)不可避免地產(chǎn)生異常,此時(shí)為了讓程序不中斷,必須要將異常捕獲掉。如果能提前得知可能會(huì)發(fā)生哪些異常,建議使用精確捕獲,如果不知道會(huì)發(fā)生哪些異常,則使用 Exception 兜底。

另外異常也可以用來傳遞信息,比如生成器。

def gen():
    yield 1
    yield 2
    return "result"

g = gen()
next(g)
next(g)
try:
    next(g)
except StopIteration as e:
    print(f"返回值: {e.value}")   # 返回值: result

如果想要拿到生成器的返回值,我們需要讓它拋出 StopIteration,然后進(jìn)行捕獲,再通過 value 屬性拿到返回值。所以,Python 是將生成器的返回值封裝到了異常里面。

之所以舉這個(gè)例子,目的是想說明,異常并非是讓人嗤之以鼻的東西,它也可以作為信息傳遞的載體。特別是在 Java 語言中,引入了 checked exception,方法的所有者還可以聲明自己會(huì)拋出什么異常,然后調(diào)用者對(duì)異常進(jìn)行處理。

在 Java 程序啟動(dòng)時(shí),拋出大量異常都是司空見慣的事情,并在相應(yīng)的調(diào)用堆棧中將信息完整地記錄下來。至此,Java 的異常不再是異常,而是一種很普遍的結(jié)構(gòu),從良性到災(zāi)難性都有所使用,異常的嚴(yán)重性由調(diào)用者來決定。

雖然在 Python 里面,異常還沒有達(dá)到像 Java 異常那么高的地位,但使用頻率也是很高的,下面我們就來剖析一下異常是怎么實(shí)現(xiàn)的?

異常的本質(zhì)是什么

Python 解釋器 = Python 編譯器 + Python 虛擬機(jī),所以異常可以由編譯器拋出,也可以由虛擬機(jī)剖出。如果是編譯器拋出的異常,那么基本上都是 SyntaxError,即語法錯(cuò)誤。

try:
    >>>
except Exception as e:
    print(e)

比如上面這段代碼,你會(huì)發(fā)現(xiàn)異常捕獲根本沒用,因?yàn)檫@是編譯階段就發(fā)生的錯(cuò)誤,而異常捕獲是在運(yùn)行時(shí)進(jìn)行的。當(dāng)然語法不對(duì)屬于低級(jí)錯(cuò)誤,所以不會(huì)留到運(yùn)行時(shí)。

然后是運(yùn)行時(shí)產(chǎn)生的異常:

try:
    1 / 0
except ZeroDivisionError:
    print("Division by zero")

像這種語法正確,但程序執(zhí)行時(shí)因邏輯出現(xiàn)問題而導(dǎo)致的異常,是可以被捕獲的。對(duì)于我們來說,關(guān)注的顯然是運(yùn)行時(shí)產(chǎn)生的隱藏,比如 TypeError、IndexError 等等。

那么問題來了,異常本質(zhì)上是什么呢?我們以列表為例,看看 IndexError 是怎么產(chǎn)生的。

lst = [1, 2, 3]
print(lst[3])
"""
IndexError: list index out of range
"""

列表的最大索引是 2,但我們?cè)L問了索引為 3 的元素,虛擬機(jī)就知道不能再執(zhí)行下去了,否則會(huì)訪問非法內(nèi)存。因此虛擬機(jī)的做法是:輸出異常信息,結(jié)束進(jìn)程。我們通過源碼來驗(yàn)證一下:

圖片圖片

在獲取列表元素時(shí)發(fā)現(xiàn)索引不合法,就知道要拋出 IndexError 了,會(huì)將異常寫入到標(biāo)準(zhǔn)錯(cuò)誤輸出當(dāng)中,并返回 NULL。正常情況下,返回值應(yīng)該指向一個(gè)合法的對(duì)象,如果為 NULL,證明出現(xiàn)異常了。

此時(shí)虛擬機(jī)會(huì)將回溯棧里的異常拋出來(就是我們?cè)诳刂婆_(tái)看到的那一抹鮮紅),然后結(jié)束進(jìn)程,這就是異常的本質(zhì)。當(dāng)然異常也是一個(gè) Python 對(duì)象,虛擬機(jī)在退出前,會(huì)寫入到 stderr 中。

異常寫入的一些 C API

當(dāng)我們用 C 編寫 Python 擴(kuò)展時(shí),如果想設(shè)置異常的話,該怎么做呢?首先設(shè)置異常之前,我們要知道有哪些異常。在 pyerrors.h 中,虛擬機(jī)內(nèi)置了大量的異常,另外 Python 一切皆對(duì)象,因此異常也是一個(gè)對(duì)象。

圖片圖片

有了異常之后,怎么寫入呢?關(guān)于異常寫入,底層也提供了相應(yīng)的 C API。

圖片圖片

相關(guān)的 API 有很多,我們來解釋一下。

"""
PyErr_SetNone:設(shè)置異常,不包含提示信息。

PyErr_SetObject:設(shè)置異常,包含提示信息(Python 字符串)。

PyErr_SetString:設(shè)置異常,包含提示信息(C 字符串)。

PyErr_Occurred:檢測(cè)回溯棧中是否有異常產(chǎn)生。

PyErr_Clear:將回溯棧中的異常清空,相當(dāng)于 Python 的異常捕獲。

PyErr_Fetch:將回溯棧中的異常清空,同時(shí)拿到它的 exc_type、exc_value、exc_tb。

PyErr_Restore:基于 exc_type、exc_value、exc_tb 設(shè)置異常。
"""

我們以 PyErr_Restore 為例,看看異常的具體設(shè)置過程。

// Python/errors.c

// PyErr_SetObject、PyErr_SetString 等等,最終都會(huì)調(diào)用 PyErr_Restore
void
PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
{
    // 獲取線程狀態(tài)對(duì)象
    PyThreadState *tstate = _PyThreadState_GET();
    _PyErr_Restore(tstate, type, value, traceback);
}

void
_PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value,
               PyObject *traceback)
{
    // 對(duì) type、value、traceback 做一些檢測(cè)
    // ...

    PyObject *old_traceback = ((PyBaseExceptionObject *)value)->traceback;
    ((PyBaseExceptionObject *)value)->traceback = traceback;
    Py_XDECREF(old_traceback);
    // 調(diào)用 _PyErr_SetRaisedException
    _PyErr_SetRaisedException(tstate, value);
    Py_DECREF(type);
}

void
_PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc)
{
    // 線程狀態(tài)對(duì)象的 current_exception 字段,負(fù)責(zé)保存當(dāng)前異常
    PyObject *old_exc = tstate->current_exception;
    // 將它設(shè)置為 exc
    tstate->current_exception = exc;
    Py_XDECREF(old_exc);
}

我們?cè)賮砜纯?PyThreadState 對(duì)象,它是與線程相關(guān)的,但它只是線程信息的一個(gè)抽象描述,而真實(shí)的線程及狀態(tài)肯定是由操作系統(tǒng)來維護(hù)和管理的。

因?yàn)樘摂M機(jī)在運(yùn)行的時(shí)候總需要另外一些與線程相關(guān)的狀態(tài)和信息,比如是否發(fā)生了異常等等,這些信息顯然操作系統(tǒng)是沒有辦法提供的。而 PyThreadState 對(duì)象正是 Python 為線程準(zhǔn)備的、在虛擬機(jī)層面保存線程狀態(tài)信息的對(duì)象(后面簡(jiǎn)稱線程狀態(tài)對(duì)象、或者線程對(duì)象)。

當(dāng)前活動(dòng)線程(OS 原生線程)對(duì)應(yīng)的 PyThreadState 對(duì)象可以通過 PyThreadState_GET 獲得,在得到了線程狀態(tài)對(duì)象之后,就將異常信息存放在里面。

關(guān)于線程相關(guān)的內(nèi)容,后續(xù)會(huì)詳細(xì)說。

traceback 是什么?

程序產(chǎn)生的異常會(huì)被記錄在線程狀態(tài)對(duì)象當(dāng)中,現(xiàn)在可以回頭看看,在跳出了分派字節(jié)碼指令的代碼塊之后,發(fā)生了什么動(dòng)作。

在 ceval.c 里面有一個(gè) _PyEval_EvalFrameDefault 函數(shù),負(fù)責(zé)執(zhí)行字節(jié)碼指令。該函數(shù)內(nèi)部有一個(gè)代碼塊,包含了每個(gè)指令的處理邏輯,執(zhí)行完畢后會(huì)跳出代碼塊。

圖片圖片

但跳出代碼塊的原因有兩種:

  • 執(zhí)行完所有的字節(jié)碼指令之后正常跳出;
  • 發(fā)生異常后跳出;

那么虛擬機(jī)如何區(qū)分是哪一種呢?很簡(jiǎn)單,通過 error 標(biāo)簽實(shí)現(xiàn),注意代碼塊里面有一個(gè) error 標(biāo)簽。

圖片圖片

如果在執(zhí)行指令的時(shí)候出現(xiàn)了異常,那么會(huì)跳轉(zhuǎn)到 error 這里,否則會(huì)跳轉(zhuǎn)到其它地方。

另外當(dāng)出現(xiàn)異常時(shí),會(huì)在線程狀態(tài)對(duì)象中將異常信息記錄下來,包括異常類型、異常值、回溯棧(traceback),這個(gè) traceback 就是在 error 標(biāo)簽中調(diào)用 PyTraceBack_Here 創(chuàng)建的。

另外可能有人不清楚 traceback 是做什么的,我們舉個(gè) Python 的例子。

def h():
    1 / 0

def g():
    h()

def f():
    g()

f()
"""
Traceback (most recent call last):
  File "/Users/.../main.py", line 10, in <module>
    f()
  File "/Users/.../main.py", line 8, in f
    g()
  File "/Users/.../main.py", line 5, in g
    h()
  File "/Users/.../main.py", line 2, in h
    1 / 0
ZeroDivisionError: division by zero
"""

這是腳本運(yùn)行時(shí)產(chǎn)生的錯(cuò)誤輸出,我們看到了函數(shù)調(diào)用的信息:比如在源代碼的哪一行調(diào)用了哪一個(gè)函數(shù),那么這些信息是從何而來的呢?沒錯(cuò),顯然是 traceback 對(duì)象。

虛擬機(jī)在處理異常的時(shí)候,會(huì)創(chuàng)建 traceback 對(duì)象,在該對(duì)象中記錄棧幀的信息。虛擬機(jī)利用該對(duì)象來將棧幀鏈表中每一個(gè)棧幀的狀態(tài)進(jìn)行可視化,可視化的結(jié)果就是上面輸出的異常信息。

而且我們發(fā)現(xiàn)輸出的信息也是一個(gè)鏈狀的結(jié)構(gòu),因?yàn)槊恳粋€(gè)棧幀都會(huì)對(duì)應(yīng)一個(gè) traceback 對(duì)象,這些 traceback 對(duì)象之間也會(huì)組成一個(gè)鏈表。

所以當(dāng)虛擬機(jī)開始處理異常的時(shí)候,它首先的動(dòng)作就是創(chuàng)建 traceback 對(duì)象,用于記錄異常發(fā)生時(shí)活動(dòng)棧幀的狀態(tài)。創(chuàng)建方式是通過 PyTraceBack_Here 函數(shù),它接收一個(gè)棧幀作為參數(shù)。

// Python/traceback.c
int
PyTraceBack_Here(PyFrameObject *frame)
{
    // 獲取當(dāng)前的異常對(duì)象
    PyObject *exc = PyErr_GetRaisedException();
    assert(PyExceptionInstance_Check(exc));
    // 拿到當(dāng)前異常的 traceback
    PyObject *tb = PyException_GetTraceback(exc);
    // 創(chuàng)建新的 traceback 對(duì)象,并和舊的 traceback 對(duì)象組成鏈表
    PyObject *newtb = _PyTraceBack_FromFrame(tb, frame);
    Py_XDECREF(tb);
    if (newtb == NULL) {
        _PyErr_ChainExceptions1(exc);
        return -1;
    }
    // 將新的 traceback 對(duì)象交給線程狀態(tài)對(duì)象
    PyException_SetTraceback(exc, newtb);
    Py_XDECREF(newtb);
    // 重新設(shè)置異常
    PyErr_SetRaisedException(exc);
    return 0;
}


// Python/errors.c
PyErr_GetRaisedException(void)
{
    PyThreadState *tstate = _PyThreadState_GET();
    return _PyErr_GetRaisedException(tstate);
}

PyObject *
_PyErr_GetRaisedException(PyThreadState *tstate) {
    // 返回當(dāng)前的異常
    PyObject *exc = tstate->current_exception;
    tstate->current_exception = NULL;
    return exc;
}

那么這個(gè) traceback 對(duì)象究竟長(zhǎng)什么樣呢?

// Include/cpython/traceback.h
typedef struct _traceback PyTracebackObject;

struct _traceback {
    PyObject_HEAD
    PyTracebackObject *tb_next;
    PyFrameObject *tb_frame;
    int tb_lasti;
    int tb_lineno;
};

里面有一個(gè) tb_next,所以很容易想到 traceback 也是一個(gè)鏈表結(jié)構(gòu)。其實(shí) traceback 對(duì)象的鏈表結(jié)構(gòu)跟棧幀對(duì)象的鏈表結(jié)構(gòu)是同構(gòu)的、或者說一一對(duì)應(yīng)的,即一個(gè)棧幀對(duì)象對(duì)應(yīng)一個(gè) traceback 對(duì)象。

traceback 創(chuàng)建

在 PyTraceBack_Here 函數(shù)中我們看到它是通過 _PyTraceBack_FromFrame 創(chuàng)建的,那么秘密就隱藏在這個(gè)函數(shù)中。

// Python/traceback.c
PyObject*
_PyTraceBack_FromFrame(PyObject *tb_next, PyFrameObject *frame)
{
    assert(tb_next == NULL || PyTraceBack_Check(tb_next));
    assert(frame != NULL);
    // 獲取最近一條執(zhí)行完畢的字節(jié)碼指令的偏移量
    int addr = _PyInterpreterFrame_LASTI(frame->f_frame) * sizeof(_Py_CODEUNIT);
    // 創(chuàng)建 traceback
    return tb_create_raw((PyTracebackObject *)tb_next, frame, addr, -1);
}


static PyObject *
tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti,
              int lineno)
{
    PyTracebackObject *tb;
    if ((next != NULL && !PyTraceBack_Check(next)) ||
                    frame == NULL || !PyFrame_Check(frame)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    // 為 traceback 對(duì)象申請(qǐng)內(nèi)存
    tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type);
    if (tb != NULL) {
        // 設(shè)置屬性
        tb->tb_next = (PyTracebackObject*)Py_XNewRef(next);
        // 注意 traceback 內(nèi)部還保存了棧幀對(duì)象
        // 所以在 Python 中,except Exception as e 之后
        // 可以通過 e.__traceback__.tb_frame 獲取棧幀
        tb->tb_frame = (PyFrameObject*)Py_XNewRef(frame);
        tb->tb_lasti = lasti;
        tb->tb_lineno = lineno;
        // 加入 GC 追蹤, 參與垃圾回收
        PyObject_GC_Track(tb);
    }
    return (PyObject *)tb;
}

tb_next 將兩個(gè) traceback 連接了起來,不過這個(gè)和棧幀的 f_back 正好相反,f_back 指向的是上一個(gè)棧幀,而 tb_next 指向的是下一個(gè) traceback。

另外在 traceback 中,還通過 tb_frame 字段和對(duì)應(yīng)的 PyFrameObject 對(duì)象建立了聯(lián)系,當(dāng)然還有最后執(zhí)行完畢時(shí)的字節(jié)碼偏移量、以及在源代碼中對(duì)應(yīng)的行號(hào)。

棧幀展開

traceback 的創(chuàng)建我們知道了,那么它和棧幀對(duì)象是怎么聯(lián)系起來的呢?我們還以之前的代碼為例,來解釋一下。

def h():
    1 / 0

def g():
    h()

def f():
    g()

f()

當(dāng)執(zhí)行到函數(shù) h 的 1 / 0 這行代碼時(shí),底層會(huì)執(zhí)行 BINARY_OP 指令。

// Include/opcode.h
#define NB_ADD                  0
#define NB_AND                  1
#define NB_FLOOR_DIVIDE         2
#define NB_LSHIFT               3
#define NB_MATRIX_MULTIPLY      4
#define NB_MULTIPLY             5
#define NB_REMAINDER            6
#define NB_OR                   7
#define NB_POWER                8
#define NB_RSHIFT               9
#define NB_SUBTRACT            10
#define NB_TRUE_DIVIDE         11
// ...


// Python/generated_cases.c.h
TARGET(BINARY_OP) {
    // ...
    // 對(duì)于除法運(yùn)算,指令參數(shù) oparg 的值是 11
    res = binary_ops[oparg](lhs, rhs);
    // ...
}


// Python/ceval.c
static const binaryfunc binary_ops[] = {
    [NB_ADD] = PyNumber_Add,
    [NB_AND] = PyNumber_And,
    [NB_FLOOR_DIVIDE] = PyNumber_FloorDivide,
    // ...
    [NB_RSHIFT] = PyNumber_Rshift,
    [NB_SUBTRACT] = PyNumber_Subtract,
    [NB_TRUE_DIVIDE] = PyNumber_TrueDivide,
    // ...
};
// 毫無疑問,binary_ops[11] 會(huì)得到 PyNumber_TrueDivide 函數(shù)


// Objects/abstract.c
PyObject *
PyNumber_TrueDivide(PyObject *v, PyObject *w)
{
    return binary_op(v, w, NB_SLOT(nb_true_divide), "/");
}

#define NB_SLOT(x) offsetof(PyNumberMethods, x)
// 最終會(huì)執(zhí)行 (&PyLong_Type) -> tp_as_methods -> nb_true_divide
// 即 long_true_divice 函數(shù),看一下它的邏輯


// Objects/longobject.c
static PyObject *
long_true_divide(PyObject *v, PyObject *w)
{
    // ...

    a_size = _PyLong_DigitCount(a);
    b_size = _PyLong_DigitCount(b);
    negate = (_PyLong_IsNegative(a)) != (_PyLong_IsNegative(b));
    if (b_size == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError,
                        "division by zero");
        goto error;
    }
    // ...

    success:
      return PyFloat_FromDouble(negate ? -result : result);

    underflow_or_zero:
      return PyFloat_FromDouble(negate ? -0.0 : 0.0);

    overflow:
      PyErr_SetString(PyExc_OverflowError,
                      "integer division result too large for a float");
    error:
      return NULL;

}

由于除數(shù)為 0,因此會(huì)通過 PyErr_SetString 設(shè)置一個(gè)異常進(jìn)去,最終將異常類型、異常值、以及 traceback 保存到線程狀態(tài)對(duì)象中。但此時(shí) traceback 實(shí)際上是為空的,因?yàn)槟壳斑€沒有涉及到 traceback 的創(chuàng)建,那么它是什么時(shí)候創(chuàng)建的呢?繼續(xù)往下看。

由于出現(xiàn)了異常,那么 long_true_divide 會(huì)返回NULL。

圖片圖片

當(dāng)返回值為 NULL 時(shí),虛擬機(jī)就意識(shí)到發(fā)生異常了,這時(shí)候會(huì)跳轉(zhuǎn)到 pop_2_error 標(biāo)簽。

圖片圖片

當(dāng)出現(xiàn)除零錯(cuò)誤時(shí),運(yùn)行時(shí)棧里面還有兩個(gè)元素,所以跳轉(zhuǎn)到 pop_2_error。將棧里的兩個(gè)元素彈出之后,進(jìn)入 error 標(biāo)簽。

在里面會(huì)先取出線程狀態(tài)對(duì)象中已有的 traceback 對(duì)象(此時(shí)為空),然后以函數(shù) h 的棧幀為參數(shù),創(chuàng)建一個(gè)新的 traceback 對(duì)象,將兩者通過 tb_next 關(guān)聯(lián)起來。最后,再替換掉線程狀態(tài)對(duì)象里面的 traceback 對(duì)象。

在虛擬機(jī)意識(shí)到有異常拋出,并創(chuàng)建了 traceback 之后,它會(huì)在當(dāng)前棧幀中尋找 try except 語句,來執(zhí)行開發(fā)人員指定的捕捉異常動(dòng)作。如果沒有找到,那么虛擬機(jī)將退出當(dāng)前的活動(dòng)棧幀,并沿著棧幀鏈回退到上一個(gè)棧幀(這里是函數(shù) g 的棧幀),在上一個(gè)棧幀中尋找 try except 語句。

就像我們之前說的,函數(shù)調(diào)用會(huì)創(chuàng)建棧幀,當(dāng)函數(shù)執(zhí)行完畢或者出現(xiàn)異常時(shí),會(huì)回退到上一級(jí)棧幀。一層一層創(chuàng)建、一層一層返回。至于回退的這個(gè)動(dòng)作,則是在 _PyEval_EvalFrameDefault 的最后完成。

圖片圖片

當(dāng)出現(xiàn)異常時(shí),虛擬機(jī)會(huì)進(jìn)入 exception_unwind 標(biāo)簽尋找異常捕獲邏輯,相關(guān)細(xì)節(jié)下一篇文章再說,這里就讓它拋出去。然后來到 exit_unwind 標(biāo)簽,將當(dāng)前線程狀態(tài)對(duì)象中的活動(dòng)棧幀,設(shè)置為上一級(jí)棧幀,從而完成棧幀回退的動(dòng)作。

當(dāng)棧幀回退時(shí),會(huì)進(jìn)入函數(shù) g 的棧幀,由于返回值為 NULL,所以知道自己調(diào)用的函數(shù) h 的內(nèi)部發(fā)生異常了(否則返回值一定會(huì)指向一個(gè)合法的 PyObject),那么繼續(xù)尋找異常捕獲語句。

對(duì)于當(dāng)前這個(gè)例子來說,顯然是找不到的,于是會(huì)從線程狀態(tài)對(duì)象中取出已有的 traceback 對(duì)象(函數(shù) h 的棧幀對(duì)應(yīng)的 traceback)。然后以函數(shù) g 的棧幀為參數(shù),創(chuàng)建新的 traceback 對(duì)象,再將兩者通過 tb_next 關(guān)聯(lián)起來,并重新設(shè)置到線程狀態(tài)對(duì)象中。

異常會(huì)沿著棧幀鏈進(jìn)行反向傳播,函數(shù) h 出現(xiàn)的異常被傳播到了函數(shù) g 中,顯然接下來函數(shù) g 要將異常傳播到函數(shù) f 中。因?yàn)楹瘮?shù) g 在無法捕獲異常時(shí),那么返回值也是 NULL,而函數(shù) f 看到返回值為 NULL 時(shí),同樣會(huì)去尋找異常捕獲語句。但是找不到,于是會(huì)從線程狀態(tài)對(duì)象中取出已有的 traceback 對(duì)象(此時(shí)是函數(shù) g 的棧幀對(duì)應(yīng)的 traceback),然后以函數(shù) f 的棧幀為參數(shù),創(chuàng)建新的 traceback 對(duì)象,再將兩者通過 tb_next 關(guān)聯(lián)起來,并重新設(shè)置到線程狀態(tài)對(duì)象中。

最后再傳播到模塊對(duì)應(yīng)的棧幀中,如果還無法捕獲發(fā)生的異常,那么虛擬機(jī)就要將異常拋出來了。

這個(gè)沿著棧幀鏈不斷回退的過程我們稱之為棧幀展開,在棧幀展開的過程中,虛擬機(jī)不斷地創(chuàng)建與各個(gè)棧幀對(duì)應(yīng)的 traceback,并將其鏈接成鏈表。

圖片圖片

由于沒有異常捕獲,那么接下來會(huì)調(diào)用 PyErr_Print。然后在 PyErr_Print 中,虛擬機(jī)取出維護(hù)的 traceback 鏈表,并進(jìn)行遍歷,將里面的信息逐個(gè)輸出到 stderr 當(dāng)中,最終就是我們?cè)?Python 中看到的異常信息。

并且打印順序是:.py文件、函數(shù)f、函數(shù)g、函數(shù)h。因?yàn)槊恳粋€(gè)棧幀對(duì)應(yīng)一個(gè) traceback,而棧幀又是往后退的,因此顯然會(huì)從 .py文件對(duì)應(yīng)的 traceback 開始打印,然后通過 tb_next 找到函數(shù)f 對(duì)應(yīng)的 traceback,依次下去。當(dāng)異常信息全部輸出完畢之后,解釋器就結(jié)束運(yùn)行了。

因此從鏈路的開始位置到結(jié)束位置,將整個(gè)調(diào)用過程都輸出出來,可以很方便地定位問題出現(xiàn)在哪里。

Traceback (most recent call last):
  File "/Users/.../main.py", line 10, in <module>
    f()
  File "/Users/.../main.py", line 8, in f
    g()
  File "/Users/.../main.py", line 5, in g
    h()
  File "/Users/.../main.py", line 2, in h
    1 / 0
ZeroDivisionError: division by zero

另外,雖然 traceback 一直在更新(因?yàn)橐獙?duì)整個(gè)調(diào)用鏈路進(jìn)行追蹤),但是異常類型和異常值始終是不變的,就是函數(shù) h 中拋出的 ZeroDivisionError: division by zero。

小結(jié)

以上就是虛擬機(jī)拋異常的過程,異常在 Python 里面也是一個(gè)對(duì)象,和其它的實(shí)例對(duì)象并無本質(zhì)區(qū)別。

exc = StopIteration("迭代結(jié)束了")
print(exc.value)  # 我是一個(gè)異常
print(exc.args)  # ('迭代結(jié)束了',)

exc = IndexError("索引越界了")
print(exc.args)  # ('索引越界了',)

exc = Exception("不知道是啥異常,總之出問題了")
print(exc.args)  # ('不知道是啥異常,總之出問題了',)

# 異常都有一個(gè) args 屬性,以元組的形式保存?zhèn)鬟f的參數(shù)

所謂拋出異常,就是將錯(cuò)誤信息輸出到 stderr 中,然后停止進(jìn)程。并且除了虛擬機(jī)內(nèi)部會(huì)拋出異常之外,我們還可以使用 raise 關(guān)鍵字手動(dòng)引發(fā)一個(gè)異常。

def judge_score(score: int):
    if score > 100 or score < 0:
        raise ValueError("Score must be between 0 and 100")

站在虛擬機(jī)的角度,score 取任何值都是合理的,但對(duì)于我們來說,希望 score 位于 0 ~ 100。那么當(dāng) score 不滿足 0 ~ 100 時(shí),可以手動(dòng) raise 一個(gè)異常。

責(zé)任編輯:武曉燕 來源: 古明地覺的編程教室
相關(guān)推薦

2024-11-11 11:21:30

虛擬機(jī)Python跳轉(zhuǎn)表

2017-11-21 18:05:00

云計(jì)算虛擬機(jī)遷移

2022-05-23 08:00:00

Windows 11虛擬機(jī)操作系統(tǒng)

2009-03-18 08:59:28

throw異常Java

2011-09-07 09:30:57

服務(wù)器虛擬機(jī)

2020-11-04 08:00:57

虛擬機(jī)stio網(wǎng)格

2020-12-15 10:44:47

Progressive實(shí)習(xí)CIO

2023-09-02 21:35:39

Linux虛擬機(jī)

2017-11-14 16:43:13

Java虛擬機(jī)線程

2023-09-03 17:05:20

虛擬機(jī)

2012-06-12 09:51:29

虛擬機(jī)

2010-02-01 15:01:34

C++拋出異常

2023-12-12 13:21:00

Java異常程序

2020-01-17 10:52:37

無服務(wù)器容器技術(shù)

2021-01-13 11:55:39

Spring代碼Java

2010-12-23 14:05:12

虛擬機(jī)

2012-04-10 10:29:29

2010-12-27 14:11:55

虛擬機(jī)配置CPU

2019-03-05 14:59:42

Java虛擬機(jī)加載類

2010-01-18 10:15:50

虛擬機(jī)ubuntu
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产一二区免费视频 | 亚洲高清在线视频 | 日本成人毛片 | 精品国产乱码久久久久久88av | 韩日精品在线观看 | 羞羞视频在线观看 | 国产一区二区在线播放 | 超碰免费在线 | 国产午夜精品一区二区三区在线观看 | 精品国产乱码久久久久久老虎 | 天堂在线一区 | 亚洲精品黄色 | 丁香婷婷久久久综合精品国产 | 久久国产精品久久久久久 | 影视一区 | 亚洲成人日韩 | 草草草网站 | 久久久久久久久久久丰满 | 美女福利视频一区 | 国产精品视频久久 | 亚洲国产成人精品一区二区 | 国产亚洲欧美另类一区二区三区 | 亚洲www啪成人一区二区 | 欧美性生活免费 | 亚洲日本乱码在线观看 | 久久专区 | 日本不卡在线视频 | 久草网站| 免费观看www7722午夜电影 | 欧美日韩在线一区二区 | 一区二区三区回区在观看免费视频 | av手机在线播放 | 欧美久久久久 | 天堂成人av | 成人免费在线视频 | 日本黄色激情视频 | 91视频一区 | 一级黄在线观看 | www中文字幕 | 成人精品一区二区户外勾搭野战 | 午夜男人的天堂 |