如何在 Django 模型中存储复数

How to store a complex number in Django model

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

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

1) 创建自定义字段:class Complex(models.CharField) 这将允许我自定义该字段的所有方面,但如果要正确完成,则需要进行大量额外的验证工作。主要的好处是单个数字由 table.

中的单个字段表示

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

当然这个问题在过去已经解决了,但是我找不到任何好的参考资料,更不用说 Django 了。

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

我还没有测试这段代码,所以它可能有问题。它基于此 SO 问题中答案的代码。 运行 代码后出现方法名称发生了一些变化。

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)

如果你的表情每次都像R + jX 你可以做出如下class

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 字段来处理。这可能取决于您的需要。

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

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

使用 PostgreSQL,您必须可以直接在数据库中实现自定义类型,包括 operators. Here's the relevant part in the Postgres docs,完成一个复数示例,同样如此。

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

老实说,我只是将复数拆分为两个 float/decimal 字段,并添加一个 属性 作为单个复数进行读写。

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

  • contribute_to_class 在 Django wiki 中被称为 deep in the Django model machinery for all the fields that are declared on the model. Generally, they might just add the field itself to the model, and maybe additional methods like get_latest_by_..., but here we're hijacking that mechanism to instead add two fields we construct within, and not the actual "self" field itself at all, as it does not need to exist as a database column. (This might break something, who knows...) Some of this mechanism is explained here

  • ComplexPropertyclass是一个property descriptor, which allows customization of what happens when the property it's "attached as" into an instance is accessed (read or written). (How descriptors work is a little bit beyond the scope of this answer, but there's a 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()),
    ],
)

如您所见,三个 ComplexField 被分解为六个 FloatField