我需要在Django模型中存储一个复数。对于那些忘记的人,这仅表示Z=R+jX
,其中R和X是表示复数的实部和虚部的实数。将有单独的数字,以及需要存储的列表。到目前为止,我的搜索还没有为列表提供良好的解决方案,因此我打算让数据库将列表作为单独的记录来处理。
我看到两个用于存储复数的选项:
1)创建一个自定义字段:class Complex(models.CharField)
这将允许我自定义该领域的所有方面,但是如果要正确完成的话,这将需要大量的额外工作来进行验证。主要优点是表中的单个字段代表单个数字。
2)让每个复数由一行表示,实部为R的float
字段,虚部为X的另一个float
字段。这种方法的缺点我需要编写一些转换器,这些转换器将根据组件创建一个复数,反之亦然。好处是数据库只会将其视为另一条记录。
当然,这个问题过去已经解决了,但是我找不到任何好的参考文献,不要在乎Django的特殊之处。
这是我在该领域的第一个漏洞,它是基于我发现的另一个涉及一些字符串操作的示例。我不清楚应该如何以及在何处执行各种验证(例如,通过添加+ 0j将一个简单的float转换为一个复数)。我还打算添加表单功能,以便该字段的行为类似于float字段,但有其他限制或要求。
我尚未测试此代码,因此可能存在问题。它基于此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)
答案 0 :(得分: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
处理结果字符串如果您有多个实部和img部分,则可以使用外键或ManyToMany Fields处理。这可能取决于您的需求。
答案 1 :(得分:1)
关于自定义字段,您可能已经在Django documentation中找到了相关部分。
自定义字段(或自定义数据库类型,见下文)是否值得,麻烦实际上取决于您需要对存储的数字执行什么操作。对于存储和偶尔的移动,您可以使用最简单的理智的解决方案(您的第二个解决方案由Tobit增强)。
使用PostgreSQL,您必须可以直接在数据库中实现自定义类型,包括operators。这是Postgres docs中的相关部分,并附有一个复杂的数字示例。
当然,您需要将新类型和运算符公开给Django。很多工作,但是您可以使用Django ORM在数据库中对各个字段进行算术运算。
答案 2 :(得分:0)
老实说,我只是将复数分为两个浮点/小数字段,并添加了一个属性以作为单个复数进行读写。
我想出了这个自定义字段,该字段最终在实际模型上作为拆分字段并注入了上述属性。
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。)
NB:我没有在运行迁移的过程中进行过测试,因此事情可能会以意想不到的方式破坏,但至少理论是合理的。 :)
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
。