[原] 深刻對比數據科學工具箱:Python 和 R 的異常處理機制

概述

異常處理,是編程語言或計算機硬件裏的一種機制,用於處理軟件或信息系統中出現的異常情況(即超出程序正常執行流程的某些特殊條件)。Python和R做爲一門編程語言天然也是有各自的異常處理機制的,異常處理機制在代碼編寫中扮演着很是關鍵的角色,卻又是許多人容易混淆的地方。對於異常機制的合理運用是直接關係到碼農飯碗的事情!因此,本文將具體介紹一下Python和R的異常處理機制,闡明兩者在異常處理機制上的異同。html

異常安全

在瞭解Python和R的異常機制以前,咱們有必要了解一下異常安全的概念。python

根據WikiPedia的文獻,一段代碼是異常安全的,若是這段代碼運行時的失敗不會產生有害後果,如內存泄露、存儲數據混淆、或無效的輸出。咱們能夠知道一段代碼的異常安全一般分爲下面五類:git

異常安全一般分爲5個層次:github

  1. 失敗透明:若是出現了異常,將不會對外進一步拋出該異常。(通常比較複雜)數據庫

  2. 強異常安全:能夠運行失敗,不過數據會回滾到代碼運行前(無反作用)編程

  3. 基本異常安全:運行失敗致使的數據變動,使得代碼運行先後數據不一致了(有反作用)segmentfault

  4. 最小異常安全:運行失敗保存了無效數據,可是還不會引發崩潰,資源不會泄露(進程不會掛)數組

  5. 異常不安全:沒有任何保證(進程可能會掛掉)緩存

從上述的5個層次來看,咱們能夠知道,在平時寫代碼的時候,對數據庫、文件、網絡等的IO操做都是須要儘可能保證無反作用的,也就是強異常安全。具體來講就是,RDBS操做在失敗的時候須要回滾機制、全部IO操做在最後要保證IO鏈接資源關閉。安全

其實和多數語言的異常機制的語法是相似的:Python和R都是經過拋出一個異常對象或一個枚舉類的值來返回一個異常;異常處理代碼的做用域由try開始,以第一個異常處理子句(catch, except等)結束;可連續出現若干個異常處理子句,每一個處理特定類型的異常。最後經過finally子句,不管是否出現異常它都將執行,用於釋放異常處理所需的一些資源。

下面將具體介紹兩者的異常處理機制。

Python 中的異常處理機制

首先,Python 是一門面向對象語言,全部的異常類都是經過繼承BaseException類來實現的,咱們亦能夠經過相應的繼承來實現自定義的異常類,好比在工做流調度中使用AirflowException,具體實現能夠直接看Airflow的源碼。

事實上,這些在咱們代碼處理範圍內的異常其實就是能夠分紅兩個部分:

  1. IO異常:由網絡抖動、磁盤文件位置變動、數據庫鏈接變動等引發的IO異常問題。

  2. 運行期異常:因爲計算或者傳輸的參數參數類型有誤、參數值異常等等發生在運行期的異常,都統一被稱爲運行期異常。正常來講,IO上的異常咱們都要有相應的try-catch-finally機制,在Python也就是以下實現:

try:
   do something with IO
except:
   do something without IO
finally:
   close IO

這裏容易犯的一個錯誤就是在except中又引入了新的IO操做,好比在except中又引入了一個API的POST請求或者數據庫寫操做等等,這樣若是在except階段又發生了異常,將致使異常信息的丟失。

另外一方面,對於可能的運行期異常則須要咱們根據具體應用場景的需求來作相應的處理,通常就是遇到一個新的問題加一個新的異常捕獲機制,固然這裏也就考驗到碼農程序設計的功利,是否可以未雨綢繆。好比數組長度的檢查,傳入字典的Key檢查等等。Python自己提供了豐富的異常處理類型而且易於拓展,正確使用將能夠顯著提高程序的魯棒性(保住碼農的飯碗)。

使用try-catch-finally機制是足夠簡單的,可是在混入returnrasie操做以後,事情就看起來變得有點複雜。

舉一個例子:

def test():
    try:
        a = 1/0
    except:
        a = 0
        raise(ValueError,"value error, the division must greater than 0")
        return a
    finally:
        a = 1
        return a
test()

你看這裏的返回應該是什麼呢?

其實,這裏的返回最後應該是 1,而except中raise的異常則會被吃掉。這也是許多人錯誤使用finanlly的一個很好的例子。

Python在執行帶有fianlly的子句時會將except內拋出的對象先緩存起來,優先執行finally中拋出的對象,若是finally中先拋出了return或者raise,那麼except段拋出的對象將看起來被吃掉了。

一個段正確的處理方式應該是這樣的:

try:
    do IO
    info = {"status":200}
except:
    info = {"status":400}
finally:
    try:
        write log(info)
    except:
        raise(SomeError,"error message")
    close IO

具體的調用棧的過程能夠參考這個更加生動的例子:

R 中的異常處理機制

R和Python最大的不一樣就是 R 本質上是一門強動態類型的非純函數式編程語言(所謂非純即存在反作用)而非面嚮對象語言。從函數式編程語言的角度上講,R和Erlang、LISP的關係比較近一些。

既然是函數式語言,處理異常也是經過函數式的,而非直接經過面向對象的方式。R 從語法上來看就略顯突兀(花括號函數式語言的一大通病):

tryCatch({
  doStuff()
  doMoreStuff()
}, some_exception = function(se) {
  recover(se)
})

若是這段用Python來表達就變成:

try:
  doStuff()
  doMoreStuff()
except SomeException, se:
  recover(se)

事實上正確運用 R 的異常處理機制反而是比較負擔小的一種方式:(R 還支持用中文字符集命名變量)

tryCatch({
  結果 <- 表達式
}, warning = function(w) {
    warning()
  ... # 運行期異常
}, error = function(e) {
    stop()
  ... # IO異常
}, finally {
    on.exit()
  ... # 資源回收
}

下面是 Hadley 大神對R的異常處理機制優勢的分析

One of R’s great features is its condition system. It serves a similar purpose to the exception handling systems in Java, Python, and C++ but is more flexible. In fact, its flexibility extends beyond error handling–conditions are more general than exceptions in that a condition can represent any occurrence during a program’s execution that may be of interest to code at different levels on the call stack. For example, in the section 「Other Uses for Conditions,」 you’ll see that conditions can be used to emit warnings without disrupting execution of the code that emits the warning while allowing code higher on the call stack to control whether the warning message is printed. For the time being, however, I’ll focus on error handling.

The condition system is more flexible than exception systems because instead of providing a two-part division between the code that signals an error and the code that handles it, the condition system splits the responsibilities into three parts–signaling a condition, handling it, and restarting. In this chapter, I’ll describe how you could use conditions in part of a hypothetical application for analyzing log files. You’ll see how you could use the condition system to allow a low-level function to detect a problem while parsing a log file and signal an error, to allow mid-level code to provide several possible ways of recovering from such an error, and to allow code at the highest level of the application to define a policy for choosing which recovery strategy to use.

個人理解是R經過條件機制,然咱們能夠選擇性的在低階函數中把warning吃掉,這樣就不至於影響高階函數的運行?條件機制將異常分爲三階段而不是兩階段:

1.異常信號捕獲
2.異常處理
3.重啓機制。

而且咱們還能夠看到在異常處理中,如何在中階函數中恢復低階函數的Error,而且在高階函數中選擇必定的恢復策略。

這段貌似我的理解有誤,還請看官指正。

參考資料

更優閱讀體驗可直接訪問原文地址:https://segmentfault.com/a/11...
做爲分享主義者(sharism),本人全部互聯網發佈的圖文均聽從CC版權,轉載請保留做者信息並註明做者 Harry Zhu 的 FinanceR專欄:https://segmentfault.com/blog...,若是涉及源代碼請註明GitHub地址:https://github.com/harryprince。微信號: harryzhustudio商業使用請聯繫做者。

相關文章
相關標籤/搜索