Django ModelDiffMixin:超出最大递归深度

问题描述 投票:0回答:1

将 Django 版本从 4.0 升级到 4.2.15 后,我收到一个奇怪的错误。 遇到的错误是:

RecursionError: maximum recursion depth exceeded while calling a Python object

ModelDiffMixin 如下所示:

from django.forms.models import model_to_dict

class ModelDiffMixin(object):

def __init__(self, *args, **kwargs):
    super(ModelDiffMixin, self).__init__(*args, **kwargs)
    self.__initial = self._dict

@property
def diff(self):
    d1 = self.__initial
    d2 = self._dict
    diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
    return dict(diffs)

@property
def has_changed(self):
    return bool(self.diff)

@property
def changed_fields(self):
    return self.diff.keys()

def get_field_diff(self, field_name):
    """
    Returns a diff for field if it's changed and None otherwise.
    """
    return self.diff.get(field_name, None)

def save(self, *args, **kwargs):
    """
    Saves model and set initial state.
    """
    super(ModelDiffMixin, self).save(*args, **kwargs)
    self.__initial = self._dict

@property
def _dict(self):
    return model_to_dict(self, fields=[field.name for field in
                         self._meta.fields])

参考这里的要点:

https://gist.github.com/goloveychuk/72499a7251e070742f00

在此处附加堆栈跟踪。我也浏览了 django 文档,似乎在模型的 init 方法中访问 django 字段可能会导致问题。

  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/query.py", line 122, in __iter__
    obj = model_cls.from_db(
          ^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/base.py", line 582, in from_db
    new = cls(*values)
          ^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/base.py", line 571, in __init__
    super().__init__()
  File "/Users/bm/Desktop/Django/bapi/core/utils.py", line 2146, in __init__
    self.__initial = self._dict
                     ^^^^^^^^^^
  File "/Users/bm/Desktop/Django/bapi/core/utils.py", line 2201, in _dict
    return model_to_dict(self, fields=[field.name for field in self._meta.fields])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/forms/models.py", line 115, in model_to_dict
    data[f.name] = f.value_from_object(instance)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/fields/__init__.py", line 1088, in value_from_object
    return getattr(obj, self.attname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/query_utils.py", line 178, in __get__
    instance.refresh_from_db(fields=[field_name])
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/base.py", line 724, in refresh_from_db
    db_instance = db_instance_qs.get()
                  ^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/query.py", line 633, in get
    num = len(clone)
          ^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/query.py", line 380, in __len__
    self._fetch_all()
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/query.py", line 1881, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/query.py", line 91, in __iter__
    results = compiler.execute_sql(
              ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1547, in execute_sql
    sql, params = self.as_sql()
                  ^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/psqlextra/compiler.py", line 76, in as_sql
    sql, params = super().as_sql(*args, **kwargs)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 734, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup(
                                       ^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 85, in pre_sql_setup
    order_by = self.get_order_by()
               ^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 457, in get_order_by
    for expr, is_ref in self._order_by_pairs():
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 339, in _order_by_pairs
    selected_exprs[expr] = pos_expr
    ~~~~~~~~~~~~~~^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/expressions.py", line 502, in __hash__
    return hash(self.identity)
                ^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/utils/functional.py", line 57, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
                                         ^^^^^^^^^^^^^^^^^^^
  File "/Users/bm/.virtualenvs/django4.2/lib/python3.11/site-packages/django/db/models/expressions.py", line 479, in identity
    constructor_signature = inspect.signature(self.__init__)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.9/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py", line 3263, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.9/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py", line 3011, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.9/Frameworks/Python.framework/Versions/3.11/lib/python3.11/inspect.py", line 2447, in _signature_from_callable
    _get_signature_of = functools.partial(_signature_from_callable,
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    RecursionError: maximum recursion depth exceeded while calling a Python object
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

在 Django 模型中访问,例如:

class Member(ModelDiffMixin):
   class Meta:
    index_together = [['facebook_id'], ['enrollment_referrer']]
    indexes = [models.Index(fields=['-created_ts'])]

   member_name = models.CharField(max_length=100, null=True, blank=True)
   middle_name = models.CharField(max_length=100, null=True, blank=True)

代码中没有进行任何更改,相同的代码似乎适用于 Django 4.0,但不适用于 Django 4.2.15。如果有人可以帮忙解决这个问题,我将非常感激。

django django-rest-framework python-3.11
1个回答
0
投票

描述:

就像 Ben 在评论中所说的那样,

self.__inital = self._dict
函数中的
init()
导致了递归,因为
_dict
尚未初始化。一种方法是将其放入
save()
方法中。

代码:

也许尝试做这样的事情:

from django.forms.models import model_to_dict

class ModelDiffMixin(object):

    def __init__(self, *args, **kwargs):
        super(ModelDiffMixin, self).__init__(*args, **kwargs)
        self.__initial = None

    def save(self, *args, **kwargs):
        super(ModelDiffMixin, self).save(*args, **kwargs)
        self.__initial = self._dict

    @property
    def diff(self):
        if self.__initial is None:
            return {}
        d1 = self.__initial
        d2 = self._dict
        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
        return dict(diffs)

    @property
    def has_changed(self):
        return bool(self.diff)

    @property
    def changed_fields(self):
        return self.diff.keys()

    def get_field_diff(self, field_name):
        """
        Returns a diff for field if it's changed and None otherwise.
        """
        return self.diff.get(field_name, None)

    @property
    def _dict(self):
        return model_to_dict(self, fields=[field.name for field in self._meta.fields])

    def refresh_initial_state(self):
        self.__initial = self._dict

措施:

请花点时间完善代码,它不会是完美的,因为我不知道代码的其余部分是什么。如果您能分享一个有效的示例,我可以更好地了解。

© www.soinside.com 2019 - 2024. All rights reserved.