如何子类化 Django TextChoices 以添加其他属性?
How can I subclass Django TextChoices to add additional attributes?
我想在代码的其他地方使用 Django 3.0 TextChoices for a models.CharField
choices option 和 Enum
。
这是我的代码的简化版本:
from django.db import models
class ValueTypeOriginal(models.TextChoices):
# I know Django will add the label for me, I am just being explicit
BOOLEAN = 'boolean', 'Boolean'
class Template(models.Model):
value_type = models.CharField(choices=ValueTypeOriginal.choices)
我想为枚举成员添加一个额外的属性,以便此调用
>>> ValueType.BOOLEAN.native_type
bool
有效。
bool
这里不是字符串,而是 built-in python function.
This blog post 描述了通过覆盖 __new__
.
用 Enum
做类似的事情
class Direction(Enum):
left = 37, (-1, 0)
up = 38, (0, -1)
right = 39, (1, 0)
down = 40, (0, 1)
def __new__(cls, keycode, vector):
obj = object.__new__(cls)
obj._value_ = keycode
obj.vector = vector
return obj
基于我试过的:
class ValueTypeModified(models.TextChoices):
BOOLEAN = ('boolean', bool), 'Boolean'
def __new__(cls, value):
obj = str.__new__(cls, value)
obj._value_, obj.native_type = value
return obj
差不多可以了。我可以访问独特的 TextChoices
属性,例如 .choices
,并且我有属性 .native_type
,但字符串比较无法正常工作。
>>> ValueTypeOriginal.BOOLEAN == 'boolean'
True
>>> ValueTypeModified.BOOLEAN == 'boolean'
False
我想我误解了 __new__
方法,但我不知道我应该做些什么。
更新
为了回应 Ethan Furman 的回答,我尝试了
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value, type):
obj = str.__new__(value)
obj._value_ = value
obj.native_type = type
return obj
但得到
TypeError: __new__() missing 1 required positional argument: 'type'
所以我回到
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(value)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
但我得到:
TypeError: str.__new__(X): X is not a type object (tuple)
那么
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
让我回到直接字符串比较失败的起点
>>> ValueTypeOriginal.BOOLEAN == 'boolean'
True
>>> ValueType.BOOLEAN == 'boolean'
False
然而,
>>> ValueType.BOOLEAN.value == 'boolean'
True
所以正确的值似乎到达那里,但枚举成员本身并没有像 ValueType(str, Enum)
那样评估,而是像 ValueType(Enum)
比较。
更新#2
我已经试过了:
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls, value)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
class ValueType(str, Choices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
为了安全起见
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = super().__new__(cls, value)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
但 none 按预期给我直接字符串比较。
更新#3
我终于明白 Ethan Furman 要我做什么了。
解法:
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls, value[0])
obj._value_ = value[0]
obj.native_type = value[1]
return obj
你快到了。部分困难在于两个参数中的第一个,即元组 ('boolean', bool)
,正在传递给 Enum
机器。
所以,我们有两个选择:
保留元组 as-is 并使用 index-access(您当前的工作解决方案):
def __new__(cls, value): # value[0] is 'boolean'; value[1] is bool
命名 __new__
中的参数 header:
def __new__(cls, svalue, type): # value is split into named arguments
请注意,我稍微更改了名称以希望有助于避免混淆。
综合起来,您的最终方法应该如下所示(使用上面的第二个选项):
def __new__(cls, svalue, type):
obj = str.__new__(cls, svalue)
obj._value_ = svalue
obj.native_type = type
return obj
注:
__new__
的第一个参数是您要创建的实例的 class -- 通常与 __new__
方法定义的相同 class . 尽管它看起来不像,__new__
是一个 classmethod
-- 它只是 special-cased 不需要 classmethod
装饰器。
这是我使用一些 metaclass 魔法所做的:
class Mwahaha(type(models.TextChoices)):
def __new__(metacls, classname, bases, classdict):
native_types = {member: classdict[member][1] for member in classdict._member_names}
classdict._member_names.clear()
for member in native_types.keys():
val = classdict[member][0]
del classdict[member]
classdict[member] = val
cls = super().__new__(metacls, classname, bases, classdict)
for member, n_t in native_types.items():
getattr(o, member).native_type = n_t
return cls
让你的class看起来像
class ValueTypeModified(models.TextChoices, metaclass=Mwahaha):
BOOLEAN = ('boolean', 'Boolean'), bool
clear
和 del
是绕过某些 Enum._dict
保护以防止覆盖枚举或属性所必需的。然而,这正是我们想要在这里做的。
可能有一种更简单的方法可以做到这一点而无需诉诸 metaclass
但我已经准备好并准备走那条路 ¯\_(ツ)_/¯
我发布了我自己的答案,这样我就可以解释我在@Ethan Furman 的帮助下学到了什么!
From your code it looks like value
is ('boolean', bool)
, so when
you do
obj = str.__new__(cls, value)
obj
ends up being "('boolean', bool)"
这意味着这会起作用,即使这不是我的本意
>>> ValueType.BOOLEAN == str(('boolean', bool))
True
同样,如果我根本不将 value
传递给 str.__new__
构造函数(即 str.__new__(cls)
),那么 obj
最终会成为空字符串 ''
,就像不带参数调用 str()
。
这意味着这会起作用,即使这不是我的本意:
>>> ValueTypeEmptyString.BOOLEAN == ''
True
说到底,真的是我对__new__
dunder method的误解。由于我正在执行 str.__new__
调用而不仅仅是一般的 object.__new__
调用,因此第一个参数应该是 str
本身或 str
的子类。在我的例子中 TextChoices
是 str 的子类,所以 ValueType
也是 str
的子类并且可以是 str.__new__
方法的第一个参数。
然后,正如 docs for __new__
解释的那样,
The remaining arguments are those passed to the object constructor expression (the call to the class).
或者换句话说,我可以将剩余的参数视为直接输入 str()
调用。由于我不想对整个元组进行字符串化,而只是对该元组的第一个元素进行字符串化,因此我应该只将第一个元素传递给 str.__new__
调用。
所以把它们放在一起:
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
# cls is <enum 'ValueType'>, a subtype of str, while value[0] is `'boolean'`
obj = str.__new__(cls, value[0])
# value[0] is again `'boolean'`
obj._value_ = value[0]
# value[1] is `bool`
obj.native_type = value[1]
return obj
ChoicesMeta
元类处理在外部元组中添加传递的 Boolean
标签的方式,以及 .choices
的其他元类魔术,并不完全我很清楚,但现在我至少有了我一直在寻找的“工作代码”并且
>>> ValueType.BOOLEAN == 'boolean'
True
有效。
我想在代码的其他地方使用 Django 3.0 TextChoices for a models.CharField
choices option 和 Enum
。
这是我的代码的简化版本:
from django.db import models
class ValueTypeOriginal(models.TextChoices):
# I know Django will add the label for me, I am just being explicit
BOOLEAN = 'boolean', 'Boolean'
class Template(models.Model):
value_type = models.CharField(choices=ValueTypeOriginal.choices)
我想为枚举成员添加一个额外的属性,以便此调用
>>> ValueType.BOOLEAN.native_type
bool
有效。
bool
这里不是字符串,而是 built-in python function.
This blog post 描述了通过覆盖 __new__
.
Enum
做类似的事情
class Direction(Enum):
left = 37, (-1, 0)
up = 38, (0, -1)
right = 39, (1, 0)
down = 40, (0, 1)
def __new__(cls, keycode, vector):
obj = object.__new__(cls)
obj._value_ = keycode
obj.vector = vector
return obj
基于我试过的:
class ValueTypeModified(models.TextChoices):
BOOLEAN = ('boolean', bool), 'Boolean'
def __new__(cls, value):
obj = str.__new__(cls, value)
obj._value_, obj.native_type = value
return obj
差不多可以了。我可以访问独特的 TextChoices
属性,例如 .choices
,并且我有属性 .native_type
,但字符串比较无法正常工作。
>>> ValueTypeOriginal.BOOLEAN == 'boolean'
True
>>> ValueTypeModified.BOOLEAN == 'boolean'
False
我想我误解了 __new__
方法,但我不知道我应该做些什么。
更新
为了回应 Ethan Furman 的回答,我尝试了
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value, type):
obj = str.__new__(value)
obj._value_ = value
obj.native_type = type
return obj
但得到
TypeError: __new__() missing 1 required positional argument: 'type'
所以我回到
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(value)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
但我得到:
TypeError: str.__new__(X): X is not a type object (tuple)
那么
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
让我回到直接字符串比较失败的起点
>>> ValueTypeOriginal.BOOLEAN == 'boolean'
True
>>> ValueType.BOOLEAN == 'boolean'
False
然而,
>>> ValueType.BOOLEAN.value == 'boolean'
True
所以正确的值似乎到达那里,但枚举成员本身并没有像 ValueType(str, Enum)
那样评估,而是像 ValueType(Enum)
比较。
更新#2
我已经试过了:
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls, value)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
class ValueType(str, Choices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
为了安全起见
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = super().__new__(cls, value)
obj._value_ = value[0]
obj.native_type = value[1]
return obj
但 none 按预期给我直接字符串比较。
更新#3 我终于明白 Ethan Furman 要我做什么了。
解法:
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
obj = str.__new__(cls, value[0])
obj._value_ = value[0]
obj.native_type = value[1]
return obj
你快到了。部分困难在于两个参数中的第一个,即元组 ('boolean', bool)
,正在传递给 Enum
机器。
所以,我们有两个选择:
保留元组 as-is 并使用 index-access(您当前的工作解决方案):
def __new__(cls, value): # value[0] is 'boolean'; value[1] is bool
命名
__new__
中的参数 header:def __new__(cls, svalue, type): # value is split into named arguments
请注意,我稍微更改了名称以希望有助于避免混淆。
综合起来,您的最终方法应该如下所示(使用上面的第二个选项):
def __new__(cls, svalue, type):
obj = str.__new__(cls, svalue)
obj._value_ = svalue
obj.native_type = type
return obj
注:
__new__
的第一个参数是您要创建的实例的 class -- 通常与 __new__
方法定义的相同 class . 尽管它看起来不像,__new__
是一个 classmethod
-- 它只是 special-cased 不需要 classmethod
装饰器。
这是我使用一些 metaclass 魔法所做的:
class Mwahaha(type(models.TextChoices)):
def __new__(metacls, classname, bases, classdict):
native_types = {member: classdict[member][1] for member in classdict._member_names}
classdict._member_names.clear()
for member in native_types.keys():
val = classdict[member][0]
del classdict[member]
classdict[member] = val
cls = super().__new__(metacls, classname, bases, classdict)
for member, n_t in native_types.items():
getattr(o, member).native_type = n_t
return cls
让你的class看起来像
class ValueTypeModified(models.TextChoices, metaclass=Mwahaha):
BOOLEAN = ('boolean', 'Boolean'), bool
clear
和 del
是绕过某些 Enum._dict
保护以防止覆盖枚举或属性所必需的。然而,这正是我们想要在这里做的。
可能有一种更简单的方法可以做到这一点而无需诉诸 metaclass
但我已经准备好并准备走那条路 ¯\_(ツ)_/¯
我发布了我自己的答案,这样我就可以解释我在@Ethan Furman 的帮助下学到了什么!
From your code it looks like
value
is('boolean', bool)
, so when you doobj = str.__new__(cls, value)
obj
ends up being"('boolean', bool)"
这意味着这会起作用,即使这不是我的本意
>>> ValueType.BOOLEAN == str(('boolean', bool))
True
同样,如果我根本不将 value
传递给 str.__new__
构造函数(即 str.__new__(cls)
),那么 obj
最终会成为空字符串 ''
,就像不带参数调用 str()
。
这意味着这会起作用,即使这不是我的本意:
>>> ValueTypeEmptyString.BOOLEAN == ''
True
说到底,真的是我对__new__
dunder method的误解。由于我正在执行 str.__new__
调用而不仅仅是一般的 object.__new__
调用,因此第一个参数应该是 str
本身或 str
的子类。在我的例子中 TextChoices
是 str 的子类,所以 ValueType
也是 str
的子类并且可以是 str.__new__
方法的第一个参数。
然后,正如 docs for __new__
解释的那样,
The remaining arguments are those passed to the object constructor expression (the call to the class).
或者换句话说,我可以将剩余的参数视为直接输入 str()
调用。由于我不想对整个元组进行字符串化,而只是对该元组的第一个元素进行字符串化,因此我应该只将第一个元素传递给 str.__new__
调用。
所以把它们放在一起:
class ValueType(TextChoices):
BOOLEAN = (('boolean', bool), 'Boolean')
def __new__(cls, value):
# cls is <enum 'ValueType'>, a subtype of str, while value[0] is `'boolean'`
obj = str.__new__(cls, value[0])
# value[0] is again `'boolean'`
obj._value_ = value[0]
# value[1] is `bool`
obj.native_type = value[1]
return obj
ChoicesMeta
元类处理在外部元组中添加传递的 Boolean
标签的方式,以及 .choices
的其他元类魔术,并不完全我很清楚,但现在我至少有了我一直在寻找的“工作代码”并且
>>> ValueType.BOOLEAN == 'boolean'
True
有效。