2017-11-15 7 views
2

私はPythonで問題があります。python copy.deepcopyのときのRecursionError

私はこの

a = ChoiceNumToName((
    (1, "running"), 
    (2, "stopped"), 
)) 
b = copy.deepcopy(a) 

を実行すると、カスタム__getattr__

class ChoiceNumToName(object): 
    def __init__(self, django_choice_tuple): 
     self.ods_choice_tuple = django_choice_tuple 
     self.choice_data = {} 
     self.choice_point = -1 
     for choice_value, choice_name in django_choice_tuple: 
      self.choice_data.setdefault(choice_name, choice_value) 

    def __getattr__(self, item): 
     if item in self.choice_data: 
      return self.choice_data[item] 
     else: 
      raise AttributeError("no attribute %s" % item) 

    def __str__(self): 
     return str(self.ods_choice_tuple) 

    def __iter__(self): 
     self.choice_point = -1 
     return self 

    def __next__(self): 
     self.choice_point += 1 
     try: 
      return self.ods_choice_tuple[self.choice_point] 
     except IndexError: 
      raise StopIteration() 

それは、この問題を解決するにはRecursionError: maximum recursion depth exceeded while calling a Python object

を上げるクラスwhisを持っているが、この

__getattr__機能を変更、サンプルです
def __getattr__(self, item): 
    if item == "__setstate__": 
     raise AttributeError(item) 
    if item in self.choice_data: 
     return self.choice_data[item] 
    else: 
     raise AttributeError("no attribute %s" % item) 

うまくいきます。

私はここ https://github.com/python-babel/flask-babel/commit/8319a7f44f4a0b97298d20ad702f7618e6bdab6a

からこのソリューションを知っている。しかし、誰が、なぜ私に言うことができますか?

答えて

0

__getstate__および__setstate__の方法は、酸洗い操作で使用されます。なぜこれは問題なのでしょうか? Python docs on copyingから:

クラスでは、pickle化を制御するために使用し、コピーを制御するために、同じインタフェースを使用することができます。

自分自身を参照する__setstate__を定義すると、再帰オブジェクトが作成されます。そのため、RecursionErrorが生成されます。

0

TLDR:__getattr__が呼び出されてからchoice_dataが無期限に再帰させるインスタンス辞書に追加される前に呼び出されます。この問題を解決するより良い方法は、__で始まる属性のAttributeErrorを直ちに生成して、他の特殊属性または内部属性をキャッチすることです。

これは、オブジェクトがコピーされたときに__init__メソッドが呼び出されないために発生します。むしろ、新しい空のオブジェクトが作成されます。この新しいオブジェクトには空の__dict__があります。 Pythonのpickleプロトコル(コピーモジュールにも使用されています)にはフック__setstate__があり、状態の適用をカスタマイズできます(通常は__dict__の内容だけですが、例えば__getstate__が提供されている場合は任意のオブジェクトです)。そのフックが存在するかどうかを確認するにはがMROにも__dict__にもないので、__getattr__が呼び出されますので、hasattr(newobj, '__setstate__')が呼び出されます。 __getattr__self.choice_dataにアクセスしようとしますが、前述のように__dict__は現在空です。これにより、__getattr__メソッドが再び呼び出され、無限再帰を開始するchoice_data属性が取得されます。

特別なケーシング__setstate__は、照会のために早めに救済することによってトリガーされるのを防ぐ__setstate__。それが失敗すると、デフォルトのコピーメカニズムが有効になり、新しいオブジェクトの__dict__が状態から初期化されます。私の心の中で特別なケーシングだけ__setstate__は最高の解決策ではありません。特殊な属性や内部属性、つまり__で始まる属性については、他の奇妙な状況が発生するのを防ぐので、AttributeErrorをすぐに呼び出すことが最善の方法だと思います。もう1つの可能性は、self.__dict__['choice_data']またはobject.__getattribute__(self, 'choice_data')と書いて、__getattr__の中で属性検索を使用しないようにすることです。また、__new__を実装してそこにオブジェクトを割り当てることで、choice_dataが存在することを保証することもできます。

関連する問題