【问题标题】:Does django's Form class maintain state?django 的 Form 类是否保持状态?
【发布时间】:2009-08-13 22:35:35
【问题描述】:

我正在使用 django 构建我的第一个表单,并且我看到了一些我真的没想到的行为。我定义了一个表单类:

class AssignmentFilterForm(forms.Form):
filters = []
filter = forms.ChoiceField()

def __init__(self, *args, **kwargs):
    super(forms.Form, self).__init__(*args, **kwargs)
    self.filters.append(PatientFilter('All'))
    self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
    self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))

    for i, f in enumerate(self.filters):
        self.fields["filter"].choices.append((i, f.name))

当我使用以下方法将此表单输出到模板时:

{{ form.as_p }}

我看到了正确的选择。但是,刷新页面后,我在选择框中看到了 3 次列表。再次点击刷新会导致列表在选择框中显示 10 次!

这是我的看法:

@login_required
def assign_test(request):
pg = PhysicianGroup.objects.get(pk=physician_group)

if request.method == 'POST':
    form = AssignmentFilterForm(request.POST)
    if form.is_valid():
        yes = False
else:
    form = AssignmentFilterForm()
    patients = pg.allPatients().order_by('bed__room__unit', 'bed__room__order', 'bed__order' )

return render_to_response('hospitalists/assign_test.html', RequestContext(request,  {'patients': patients, 'form': form,}))

我做错了什么?

谢谢,皮特

【问题讨论】:

    标签: python django


    【解决方案1】:

    这其实是 Python 的一个吸引很多人的特性。

    当您像使用filters = [] 一样在类上定义变量时,表达式的右半部分在最初定义类时被计算。因此,当您的代码首次运行时,它将在内存中创建一个新列表并返回对该列表的引用。因此,每个AssignmentFilterForm 实例都有自己的过滤器变量,但它们都将指向内存中的同一个列表。要解决这个问题,只需将 self.filters 的初始化移动到您的 __init__ 方法中。

    大多数情况下,您不会遇到此问题,因为您使用的类型未存储为参考。数字、布尔值等被存储为它们的值。字符串是通过引用存储的,但字符串是不可变的,这意味着每次更改字符串并返回新的引用时都必须在内存中创建一个新字符串。

    指针在脚本语言中并不经常出现,因此一开始往往会让人感到困惑。

    这是一个简单的 IDLE 会话示例,用于显示正在发生的事情

    >>> class Test():
        myList = []
        def __init__( self ):
            self.myList.append( "a" )
    
    
    >>> Test.myList
    []
    >>> test1 = Test()
    >>> Test.myList
    ['a']
    >>> test1.myList
    ['a']
    >>> test2 = Test()
    >>> test2.myList
    ['a', 'a']
    >>> test1.myList
    ['a', 'a']
    >>> Test.myList
    ['a', 'a']
    

    【讨论】:

    • 感谢您的解释。那么为什么 django 会为字段选择 per-class vars,我该如何更改呢?
    • 文档中是否有您正在查看的特定页面。在所有表单示例中,我看到他们从未使用过__init__。我相信在这种情况下,您实际上可能想要消除 __init__,并将初始化作为类定义的一部分。
    • 我认为对数字、布尔值等的引用也会被存储,但是和字符串一样,数字在 Python 中也是不可变的。
    【解决方案2】:

    我拿起了 Pro Django 这本书来回答这个问题。顺便说一句,这是一本很棒的书,我强烈推荐它!

    解决方案是让选择字段和我的助手 var 都成为实例变量:

    class AssignmentFilterForm(forms.Form):
    def __init__(self, pg, request = None):
        super(forms.Form, self).__init__(request)
        self.filters = []
    
        self.filters.append(PatientFilter('All'))
        self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
        self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))
        self.addPhysicians(pg)
    
        self.fields['filter'] = forms.ChoiceField()
        for i, f in enumerate(self.filters):
            self.fields['filter'].choices.append((i, f.name))
    

    清除选项是可行的,但肯定会导致线程问题。

    【讨论】:

      【解决方案3】:

      您将附加到 PER-CLASS 变量 self.filters。通过在 __init__ 的开头执行 self.filters = [] 将其变为 PER-INSTANCE 变量。

      【讨论】:

      • 大声笑,谢谢。语言新手。发生同样的问题是因为过滤器也是静态的。我该如何解决?
      • __init__ 中的循环之前,执行self.fields['filter'].choices = [] 以清理您之前在“上一轮”中拥有的内容。可能本质上不是线程安全的,因此如果您使用多线程,则需要谨慎,但无论如何,线程对于语言初学者来说都是一个雷区,所以如果您可以避免它,您会更快乐。
      • 大声笑,我不是初学者,只是学习这种语言。如何使过滤器成为实例变量?我现在真的很困惑为什么 django 的表单文档使用静态变量。
      • @slypete,Django 表单中的字段确实需要是类变量(Django 有一个元类来处理这个问题!)。如果您是一位经验丰富的程序员,docs.djangoproject.com/en/1.0/topics/forms 应该提供您需要的所有详细信息(如果您需要一些 Python 技巧来帮助您,我总是很乐意提供帮助!-)
      【解决方案4】:

      从其他一些答案中澄清一下:

      字段是并且必须是类变量。他们通过元类来完成各种事情,这是定义它们的正确方法。

      但是,您的 filters 变量不必是类 var。它可以很容易地成为一个实例变量——只需从类中删除定义并将其放入__init__。或者,也许更好的是,根本不让它成为一个属性——只是__init__ 中的一个本地变量。然后,不要附加到filters.choices,而是重新分配它。

      def __init__(self, *args, **kwargs):
              super(forms.Form, self).__init__(*args, **kwargs)
              filters = []
              filters.append(PatientFilter('All'))
              filters.append(PatientFilter('Assigned', 'service__isnull', False))
              filters.append(PatientFilter('Unassigned', 'service__isnull', True))
      
              self.fields["filter"].choices = [(i, f.name) for i, f in enumerate(filters)]    
      

      【讨论】:

        【解决方案5】:

        如上所述,您需要将过滤器初始化为实例变量:

        def __init__(...):
            self.filters = []
            self.filters.append(...)
            # ...
        

        如果你想了解更多关于 Form 类的工作原理,你应该阅读 Django wiki 中的这个页面:

        它讨论了 Model 类的内部结构,但您会发现字段的一般设置与 Form 有点相似(减去数据库的内容)。这有点过时(2006 年),但我认为基本原则仍然适用。如果你是新手,元类的东西可能会有点混乱。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-11
          • 2021-10-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-24
          相关资源
          最近更新 更多