2013-03-22 52 views
27

我會與很多深度嵌套的json進行交互,我沒有寫,並且想讓我的python腳本更加「寬容」到無效輸入。我發現自己的寫作涉及嘗試(除了塊),而只是將可疑函數包裝起來。一般裝飾器包裝嘗試除了在python?

我知道吞下異常是一個糟糕的政策,但我寧願他們在稍後打印和分析,而不是實際停止執行。這更有價值,在我的用例中繼續執行循環而不是獲取所有密鑰。

這裏是我現在在做什麼:

try: 
    item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST() 
except: 
    item['a'] = '' 
try: 
    item['b'] = OBJECT_THAT_DOESNT_EXIST.get('key2') 
except: 
    item['b'] = '' 
try: 
    item['c'] = func1(ARGUMENT_THAT_DOESNT_EXIST) 
except: 
    item['c'] = '' 
... 
try: 
    item['z'] = FUNCTION_THAT_DOESNT_EXIST(myobject.method()) 
except: 
    item['z'] = '' 

這是我想要的東西,(1):

item['a'] = f(myobject.get('key').get('subkey')) 
item['b'] = f(myobject.get('key2')) 
item['c'] = f(func1(myobject) 
... 

或(2):

@f 
def get_stuff(): 
    item={} 
    item['a'] = myobject.get('key').get('subkey') 
    item['b'] = myobject.get('key2') 
    item['c'] = func1(myobject) 
    ... 
    return(item) 

。 ..其中我可以包裝單個數據項(1),或主函數(2),在一些功能,轉變執行 - 停止異常到空字段,打印到stdo UT。前者可能是一種逐項跳過 - 如果該鍵不可用,則記錄空白並繼續前進 - 後者是一個行跳過,如果任何字段不起作用,則整個記錄是跳過。

我的理解是某種包裝應該能夠解決這個問題。以下是我用包裝紙試過的:

def f(func): 
    def silenceit(): 
     try: 
     func(*args,**kwargs) 
     except: 
     print('Error') 
     return(silenceit) 

這就是爲什麼它不起作用。調用不存在的函數,它沒有的try-catch它扔掉:

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

之前,我甚至添加一個空白的返回值,我希望得到它的正確的try-catch。如果該功能已經工作,這將打印出「錯誤」,對吧?

包裝函數是否是正確的方法?

UPDATE

我已經有很多低於真正有用的,有益的答案,並感謝你爲他們 - 但是我已經編輯我上面所用的例子來說明,我試圖以捕捉更多的嵌套關鍵錯誤,我正在尋找一種功能,包裝一個try-catch ...

  1. 當一個方法不存在。
  2. 當一個對象不存在時,並且正在獲取一個對其調用的方法。
  3. 當一個不存在的對象被調用爲一個函數的參數時。
  4. 任何這些東西的任何組合。
  5. 獎勵,當一個功能不存在。
+0

對於具體訪問嵌套的JSON,你可能想看看[safeJSON](https://github.com/NYTimes/safejson)。這通過有效地包裝對象'myobject'來工作。 – BrenBarn 2014-12-14 21:20:27

回答

6

這取決於您期望的例外。

如果你只用例是get(),你可以做

item['b'] = myobject.get('key2', '') 

對於其他情況下,你的裝飾方法可能是有用的,但不是你做的方式。

我會盡力給你看:

def f(func): 
    def silenceit(*args, **kwargs): # takes all kinds of arguments 
     try: 
     return func(*args, **kwargs) # returns func's result 
     except Exeption, e: 
     print('Error:', e) 
     return e # not the best way, maybe we'd better return None 
        # or a wrapper object containing e. 
    return silenceit # on the correct level 

然而,f(some_undefined_function())將無法​​正常工作,因爲

一)f()尚不活躍在executon時間和

b )它使用錯誤。正確的方法是包裝該功能,然後調用它:f(function_to_wrap)()

A 「拉姆達層」 將在這裏幫助:

wrapped_f = f(lambda: my_function()) 

包裝lambda函數,後者又調用一個不存在的功能。調用wrapped_f()導致調用調用lambda的包裝,該lambda嘗試呼叫my_function()。如果這不存在,lambda引發包裝器捕獲的異常。

這是有效的,因爲名稱my_function在定義lambda時但未執行時才執行。然後這個執行被保護,並被函數f()包裝。所以異常發生在lambda內部,並傳播到由裝飾器提供的包裝函數,該裝飾器正常處理它。

此舉對lambda函數裏面,如果你嘗試用包裝材料來代替lambda函數類似

g = lambda function: lambda *a, **k: function(*a, **k) 

接着是

f(g(my_function))(arguments) 

因爲這裏的名稱解析不起作用「回到表面」:my_function無法解析,這發生在g()甚至f()被調用之前。所以它不起作用。

如果你試圖做類似

g(print)(x.get('fail')) 

如果你沒有x它不能正常工作爲好,因爲g()保護print,不x

如果你想在這裏保護x,你就必須做

value = f(lambda: x.get('fail')) 

因爲f()提供的包裝調用它拋出一個異常,然後沉默lambda函數。

+0

這很有幫助,但我仍在努力。 (異常是拼寫錯誤,但得到它。)有一點,有沒有一種方法,而不是用lambda包裝,我可以做另一個函數,g()這樣做lambda包裝在我的泛型函數,包括* args和** kwargs?所以我可以調用g(anyfunction()),它會有我正在尋找的全部效果? – Mittenchops 2013-03-24 15:35:49

+0

當我嘗試它時,我做了>>> wrapped_p = f(lambda * z,** z:func(* z,** z))'並且得到了'File'「,第1行 SyntaxError:重複的參數函數定義中的'z' – Mittenchops 2013-03-24 15:50:21

+0

當然;你可以做一個'g = lambda函數:lambda * a,** k:function(* a,** k)',它可以讓你執行'g(anyfunction)()'。 (只有這樣:你不想包裝函數的結果,但函數本身。) – glglgl 2013-03-24 18:55:13

8

在您的情況下,您首先評估meow調用(不存在)的值,然後將其包裝在裝飾器中。這並不是那種方式。

首先在包裝之前引發異常,然後包裝器被錯誤縮進(silenceit不應返回自身)。你可能想這樣做:

def hardfail(): 
    return meow() # meow doesn't exist 

def f(func): 
    def wrapper(): 
    try: 
     func() 
    except: 
     print 'error' 
    return wrapper 

softfail =f(hardfail) 

輸出:

>>> softfail() 
error 

>>> hardfail() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 2, in hardfail 
NameError: global name 'meow' is not defined 

反正你的情況,我不明白你爲什麼不使用一個簡單的方法,如

def get_subkey(obj, key, subkey): 
    try: 
    return obj.get(key).get(subkey, '') 
    except AttributeError: 
    return '' 

並在代碼中:

item['a'] = get_subkey(myobject, 'key', 'subkey') 

編輯:

如果你想要的東西,將在任何深度工作。你可以這樣做:

def get_from_object(obj, *keys): 
    try: 
    value = obj 
    for k in keys: 
     value = value.get(k) 
    return value 
    except AttributeError: 
    return '' 

你會叫:

>>> d = {1:{2:{3:{4:5}}}} 
>>> get_from_object(d, 1, 2, 3, 4) 
5 
>>> get_from_object(d, 1, 2, 7) 
'' 
>>> get_from_object(d, 1, 2, 3, 4, 5, 6, 7) 
'' 
>>> get_from_object(d, 1, 2, 3) 
{4: 5} 

和使用代碼

item['a'] = get_from_object(obj, 2, 3) 

順便說一句,在個人的觀點我也很喜歡使用contextmanager @cravoori解決方案。但是這意味着每次有三行代碼:

item['a'] = '' 
with ignored(AttributeError): 
    item['a'] = obj.get(2).get(3) 
+0

謝謝!在錯誤的地方回來修復了我以爲我在做什麼,但沒有。但是我仍然對meow()的內部異常在包裝之前被調用的方式感到迷茫。我不能使用簡單方法的原因是我在幾個不同的深度調用,使用具有不同屬性的幾個不同的對象。我會爲每個作業寫一個函數,這會像試圖捕捉一樣麻煩。所以,我正在尋找一些可以一般地捕獲失敗函數並返回'',將錯誤輸出到stdout的東西。 – Mittenchops 2013-03-22 17:33:10

+0

進行了更改以顯示更通用的版本 – astreal 2013-03-23 00:40:44

2

爲什麼不只是使用循環?

for dst_key, src_key in (('a', 'key'), ('b', 'key2')): 
    try: 
     item[dst_key] = myobject.get(src_key).get('subkey') 
    except Exception: # or KeyError? 
     item[dst_key] = '' 

,或者您希望寫一個小幫手:

def get_value(obj, key): 
    try: 
     return obj.get(key).get('subkey') 
    except Exception: 
     return '' 

此外,如果有,你需要得到的價值和助手功能將更加合理,一些地方你可以結合這兩種解決方案。

不確定您是否確實需要裝飾器來解決問題。

+0

我試圖使其更通用,不僅僅適用於一級深度缺失密鑰,而且還有很多可能因多種原因無法分配數據的功能。 – Mittenchops 2013-03-22 14:53:38

27

你可以使用一個defaultdict和the context manager approach as outlined in Raymond Hettinger's PyCon 2013 presentation

from collections import defaultdict 
from contextlib import contextmanager 

@contextmanager 
def ignored(*exceptions): 
    try: 
    yield 
    except exceptions: 
    pass 

item = defaultdict(str) 

obj = dict() 
with ignored(Exception): 
    item['a'] = obj.get(2).get(3) 

print item['a'] 

obj[2] = dict() 
obj[2][3] = 4 

with ignored(Exception): 
    item['a'] = obj.get(2).get(3) 

print item['a'] 
+0

嗯,這是整潔。我要調查這一點。謝謝。 – Mittenchops 2013-03-24 15:38:04

+0

鏈接已關閉。任何想法在哪裏找到它? – jdennison 2013-09-24 16:16:42

+0

@jdennison,更正鏈接 – iruvar 2013-09-24 16:26:49

14

它非常容易使用可配置的裝飾來實現。

def get_decorator(errors=(Exception,), default_value=''): 

    def decorator(func): 

     def new_func(*args, **kwargs): 
      try: 
       return func(*args, **kwargs) 
      except errors, e: 
       print "Got error! ", repr(e) 
       return default_value 

     return new_func 

    return decorator 

f = get_decorator((KeyError, NameError), default_value='default') 
a = {} 

@f 
def example1(a): 
    return a['b'] 

@f 
def example2(a): 
    return doesnt_exist() 

print example1(a) 
print example2(a) 

只需傳遞給get_decorator元組,其中包含要靜默的錯誤類型以及要返回的默認值。 輸出將是

Got error! KeyError('b',) 
default 
Got error! NameError("global name 'doesnt_exist' is not defined",) 
default 

編輯:由於蒂諾我改變錯誤的默認值的元組與基本例外防止錯誤。

+0

原諒我缺乏這方面的知識。我試圖在我的腳本中使用它,但它沒有給我我要找的結果。我的錯誤是'AttributeError:'NoneType'object has no attribute text'from f(soup.find(「span」,class _ ='xxx')。text)。我將裝飾器定義爲'f = get_decorator(errors =(AttributeError,),default_value =「#NA」)'。我在這裏做錯了什麼? – MattV 2014-12-12 17:02:28

+0

@MVersteeg:您需要將'@ f'應用於返回產生異常的'soup.find(「span」,class _ ='xxx')。text「表達式的值的函數 - 如答案中的例子。 – martineau 2014-12-13 17:59:53

+0

Upvoted,雖然我會將get_decorator()的簽名更改爲「def get_decorator(default_value ='',* errors)」來簡化它的調用。 – martineau 2014-12-13 18:44:58

5

由於您正在處理大量破損的代碼,因此在這種情況下使用eval可能是不容易的。

def my_eval(code): 
    try: 
    return eval(code) 
    except: # Can catch more specific exceptions here. 
    return '' 

然後換你的所有可能存在問題的語句:

item['a'] = my_eval("""myobject.get('key').get('subkey')""") 
item['b'] = my_eval("""myobject.get('key2')""") 
item['c'] = my_eval("""func1(myobject)""") 
13

有很多很好的答案在這裏,但我沒有看到任何解決的,你是否能夠通過裝飾做到這一點的問題。

簡短的回答是「不」,至少不會對您的代碼進行結構性更改。裝飾者在功能層面上運行,而不是在單個語句上運行。因此,爲了使用裝飾器,您需要將每個要裝飾的語句移動到它自己的函數中。

但請注意,您不能只將任務本身放在裝飾函數中。您需要從裝飾函數返回rhs表達式(要分配的值),然後在外部執行任務。

爲了把這個在您的示例代碼方面,人們可能會寫代碼有以下模式:

@return_on_failure('') 
def computeA(): 
    item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST() 

item["a"] = computeA() 

return_on_failure可能是這樣的:

def return_on_failure(value): 
    def decorate(f): 
    def applicator(*args, **kwargs) 
     try: 
     f(*args,**kwargs) 
     except: 
     print('Error') 

    return applicator 

    return decorate