【问题标题】:How to store a complex number in Django model如何在 Django 模型中存储复数
【发布时间】:2019-08-09 06:41:19
【问题描述】:

我需要在 Django 模型中存储一个复数。对于那些忘记的人,这仅仅意味着Z=R+jX,其中 R 和 X 是表示复数的实部和虚部的实数。会有单独的号码,以及需要存储的列表。到目前为止,我的搜索还没有为列表提供好的解决方案,所以我打算让数据库将列表作为单独的记录来处理。

我看到了两个存储复数的选项:

1) 创建自定义字段:class Complex(models.CharField) 这将允许我自定义该字段的所有方面,但如果要正确完成,那将是大量额外的验证工作。主要优点是单个数字由表中的单个字段表示。

2) 让每个复数由一行表示,float 字段表示实部 R,另一个 float 字段表示虚部 X。这种方法的缺点是我会需要编写一些转换器来从组件中创建一个复数,反之亦然。好处是数据库只会将其视为另一条记录。

这个问题过去肯定已经解决了,但我找不到任何好的参考资料,更不用说 Django 的特别之处了。

这是我在该领域的第一次破解,它基于我发现的另一个涉及一些字符串操作的示例。我不清楚应该如何以及在何处执行各种验证(例如通过添加 +0j 将简单的浮点数强制为复数)。我还打算添加表单功能,以便该字段的行为类似于浮点字段,但有额外的限制或要求。

我尚未测试此代码,因此可能存在问题。它基于this SO question中答案的代码。运行代码后,似乎方法名称发生了一些变化。

What is the most efficient way to store a list in the Django models?

class ComplexField(models.CharField):

    description = 'A complex number represented as a string'

    def __init__(self, *args, **kwargs):
        kwargs['verbose_name'] = 'Complex Number'
        kwargs['max_length'] = 64
        kwargs['default'] = '0+0j'

        super().__init__(*args, **kwargs)

    def to_python(self, value):
        if not value: return
        if isinstance(value, complex):
            return value
        return complex(value)

    def get_db_prep_value(self, value):
        if not value: return
        assert(isinstance(value, complex))
        return str(item)[1:-1]

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

【问题讨论】:

  • 听起来您正在准确描述自定义字段要解决的场景。最好让 to/from 转换尽可能靠近数据库,这样您就不必记住在模板或视图中将字符串转换为复数。
  • 好的,我需要为自定义字段找到一个很好的示例。我很惊讶在我的搜索中没有弹出任何内容。 “复杂”返回的大部分内容都被视为“困难”,因此并不容易。
  • 只要您不需要以字符串以外的任何其他方式查询 ComplexField,您的方法看起来不错。如果您需要以更复杂的方式进行查询,它会变得更加复杂。

标签: python django django-models django-custom-field


【解决方案1】:

如果你的表达方式每次都像 R + jX 你可以做以下类

class ComplexNumber(models.Model):
    real_number = models.FloatField('Real number part')
    img_number = models.FloatFoeld('Img number part')

    def __str__(self):
        return complex(self.real_number, self.img_number)

并使用 python see here 处理结果字符串

如果您有多个 real 和 img 部分,您可以使用外键或 ManyToMany 字段来处理。这可能取决于您的需要。

【讨论】:

  • 感谢您扩展 @Tobit。每条记录都是一个数字,因此唯一需要的 ForeignKey 是将它与所有者对象绑定的那个。重复数据删除似乎也是一个想法,但我暂时不会触及它。
  • @Brian,很高兴为您提供帮助。也许您可以向我们展示最终解决方案或将我的答案标记为正确。
  • 这会导致对每个 ComplexNumber 进行额外的查找,这听起来有点不幸。
  • @AKX 你能解释一下你的意思吗?如果您尝试将某些内容存储在数据库中,您必须从数据库中获取它吗?该类存储完整的复数。否则,您可以使用过滤器、注释或其他东西来只查找多个 ComplexNumber。我不明白你的评论。
  • 我假设 OP 的目的是能够在模型中存储复数,就像它们存储浮点数或小数一样,即在列中,而不是作为对另一个表的外键引用。
【解决方案2】:

关于自定义字段,您可能已经在Django documentation 中找到了相关部分。

自定义字段(或自定义数据库类型,见下文)是否值得麻烦实际上取决于您需要对存储的数字做什么。对于存储和一些偶尔的推挤,您可以使用最简单的理智解决方案(Tobit 增强的第二个解决方案)。

使用 PostgreSQL,您必须能够直接在数据库中实现自定义类型,包括 operators。这是Postgres docs 中的相关部分,并附有一个复数示例。

当然,您需要向 Django 公开新类型和运算符。相当多的工作,但是您可以使用 Django ORM 对数据库中的各个字段进行算术运算。

【讨论】:

  • 我正在使用 MariaDB,并希望暂时保持与 DB 无关。我正在寻找一种平衡前期工作的解决方案,能够在项目中走得足够远,以便在需要时重新设计一些东西(我仍在学习 Django,我觉得我只是触及了表面)。我倾向于@Tobit 扩展的内容。我需要更多地了解模型方法。
  • 我最终实现了一个自定义字段,它运行良好,以至于我忘记了它。
  • 很高兴听到这个消息!您可以发布您的解决方案作为答案;这将有助于其他人解决这个问题。
【解决方案3】:

老实说,我只是将复数拆分为两个浮点/十进制字段,并添加一个用于读取和写入的属性作为单个复数。

我想出了这个自定义字段,它最终成为实际模型上的一个拆分字段,并且也注入了上述属性。

  • 对于模型上声明的所有字段,contribute_to_class 被称为 deep in the Django model machinery。通常,他们可能只是将字段本身添加到模型中,也可能添加诸如get_latest_by_... 之类的其他方法,但在这里我们劫持了该机制来添加我们在其中构造的两个字段,而不是实际的“self”字段本身,因为它不需要作为数据库列存在。 (这可能会破坏某些东西,谁知道...) Django wiki 中的 here 解释了一些这种机制。

  • ComplexProperty 类是 property descriptor,它允许自定义在访问(读取或写入)它“附加”到实例中的属性时发生的情况。 (描述符的工作原理有点超出这个答案的范围,但有一个how-to guide in the Python docs。)

注意:除了运行迁移之外,我没有对此进行测试,所以事情可能会以意想不到的方式被破坏,但至少理论是合理的。 :)

from django.db import models


class ComplexField(models.Field):
    def __init__(self, **kwargs):
        self.field_class = kwargs.pop('field_class', models.FloatField)
        self.field_kwargs = kwargs.pop('field_kwargs', {})
        super().__init__(**kwargs)

    def contribute_to_class(self, cls, name, private_only=False):
        for field in (
            self.field_class(name=name + '_real', **self.field_kwargs),
            self.field_class(name=name + '_imag', **self.field_kwargs),
        ):
            field.contribute_to_class(cls, field.name)

        setattr(cls, name, ComplexProperty(name))


class ComplexProperty:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        if not instance:
            return self
        real = getattr(instance, self.name + '_real')
        imag = getattr(instance, self.name + '_imag')
        return complex(real, imag)

    def __set__(self, instance, value: complex):
        setattr(instance, self.name + '_real', value.real)
        setattr(instance, self.name + '_imag', value.imag)


class Test(models.Model):
    num1 = ComplexField()
    num2 = ComplexField()
    num3 = ComplexField()


这个迁移看起来像

migrations.CreateModel(
    name="Test",
    fields=[
        (
            "id",
            models.AutoField(
                auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
            ),
        ),
        ("num1_real", models.FloatField()),
        ("num1_imag", models.FloatField()),
        ("num2_real", models.FloatField()),
        ("num2_imag", models.FloatField()),
        ("num3_real", models.FloatField()),
        ("num3_imag", models.FloatField()),
    ],
)

如您所见,三个ComplexFields 被分解为六个FloatFields。

【讨论】:

  • 你能提供更详细的描述吗?这听起来像是我的选项 2)但我一点也不熟悉属性,在读/写意义上的属性(特别是 contribute_to_class 方法)。我了解您只是在定义 setter 和 getter 的属性。此外,这种 setter/getter 方法能否很好地扩展表单/输入验证?
  • @Brian There – 希望对您有所帮助。
  • 我想学习,所以我很欣赏额外的细节。我看到迁移产生了_real_imag 列,并且管理界面产生了两个表单字段。这是由于 contribute_to_class 方法,还是属性,或两者兼而有之?
  • contribute_to_class 方法添加了这两个字段。老实说,我什至没有真正考虑过管理员和其他自动生成的 UI(例如 ModelForms)最终会使用两个单独的字段,所以这个解决方案在这个意义上肯定有点笨拙。
  • 老实说,很少有数据会以表格形式显示或编辑。作为将设置或结果保存到数据库的一部分,视图或模型本身将读取和写入数据库。将数据作为实数和虚数字段输入还避免了对空格、有效数字等的许多其他验证,这比在 CharField 或类似的东西中验证整个字符串是一个显着的改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-16
  • 2016-02-11
  • 2011-05-16
  • 2019-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多