67

私が書いていない深くネストされたjsonの多くとやり取りしたいので、私のpythonスクリプトを無効な入力に対してより「寛容」にしたいと考えています。私は自分自身が複雑な try-except ブロックを書いていることに気付き、疑わしい関数をラップするだけで済みます。

例外を飲み込むのが悪い方針であることは理解していますが、実際に実行を停止するよりも、後で出力して分析することをお勧めします。私のユースケースでは、すべてのキーを取得するよりも、ループを介して実行を続ける方が価値があります。

これが私が今していることです:

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) をラップして、実行停止例外を空のフィールドに変換し、stdout に出力します。前者は項目ごとのスキップのようなものです - そのキーが利用できない場合、空白をログに記録して先に進みます - 後者は行スキップで、フィールドのいずれかが機能しない場合、レコード全体がスキップしました。

私の理解では、ある種のラッパーがこれを修正できるはずです。ラッパーを使用して、私が試したのは次のとおりです。

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 を正しく取得したいと考えています。関数が機能した場合、これは「エラー」と出力されますよね?

ここでラッパー関数は正しいアプローチですか?

アップデート

以下に非常に有用で役立つ回答がたくさんありました。それらに感謝します。 m 特に try-catch をラップする関数を探しています...

  1. メソッドが存在しない場合。
  2. オブジェクトが存在せず、メソッドが呼び出されている場合。
  3. 存在しないオブジェクトが関数の引数として呼び出されている場合。
  4. これらの任意の組み合わせ。
  5. 関数が存在しない場合のボーナス。
4

10 に答える 10

47

Raymond Hettinger の PyCon 2013 プレゼンテーションで説明されているように、defaultdict とコンテキスト マネージャーのアプローチを使用できます。

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']
于 2013-03-22T14:50:33.170 に答える
45

ここには多くの良い答えがありますが、デコレータを介してこれを達成できるかどうかという問題に対処するものは見当たりませんでした.

短い答えは「いいえ」です。少なくとも、コードの構造的な変更は必要ありません。デコレーターは、個々のステートメントではなく、関数レベルで動作します。したがって、デコレーターを使用するには、装飾する各ステートメントを独自の関数に移動する必要があります。

ただし、割り当て自体を装飾された関数内に置くことはできないことに注意してください。装飾された関数から 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:
         return f(*args,**kwargs)
      except:
         print('Error')
         return value

    return applicator

  return decorate
于 2014-12-16T08:01:09.893 に答える
27

構成可能なデコレーターを使用すると、非常に簡単に実現できます。

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

編集:マルティノーのおかげで、エラーを防ぐために、エラーのデフォルト値を基本的な例外を持つタプルに変更しました。

于 2014-12-12T15:30:58.490 に答える
10

予想される例外によって異なります。

あなたの唯一のユースケースがである場合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())動作しません。

a)f()実行時にまだアクティブではなく、

b) 使い方が間違っている。正しい方法は、関数をラップしてから呼び出すことです: f(function_to_wrap)().

ここでは「ラムダのレイヤー」が役立ちます。

wrapped_f = f(lambda: my_function())

存在しない関数を呼び出すラムダ関数をラップします。を呼び出すwrapped_f()と、呼び出しを試みるラムダを呼び出すラッパーが呼び出されますmy_function()。これが存在しない場合、ラムダはラッパーによってキャッチされる例外を発生させます。

これは、名前my_functionがラムダの定義時に実行されるのではなく、実行時に実行されるため機能します。そして、この実行は関数によって保護され、ラップされますf()。そのため、例外はラムダ内で発生し、デコレータによって提供されるラッピング関数に伝播され、デコレータが適切に処理します。

ラムダ関数を次のようなラッパーに置き換えようとすると、ラムダ関数内へのこの移動は機能しません。

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()printx

xここを守りたいならやらなきゃ

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

によって提供されるラッパーは、f()そのラムダ関数を呼び出して例外を発生させ、その後沈黙させるためです。

于 2013-03-22T14:23:41.420 に答える
9

あなたの場合、最初に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 ソリューションも気に入っています。しかし、これは毎回 3 行のコードが必要になることを意味します。

item['a'] = ''
with ignored(AttributeError):
  item['a'] = obj.get(2).get(3) 
于 2013-03-22T14:27:03.630 に答える
4

多くの壊れたコードを扱っているので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)""")
于 2014-12-16T01:57:23.303 に答える
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 ''

また、値を取得する必要がある場所がいくつかあり、ヘルパー関数がより合理的である場合は、両方のソリューションを組み合わせることができます。

問題に実際にデコレータが必要かどうかはわかりません。

于 2013-03-22T14:32:04.717 に答える