Python:快速而肮脏的数据类型 (DTO)

Posted

技术标签:

【中文标题】Python:快速而肮脏的数据类型 (DTO)【英文标题】:Python: Quick and dirty datatypes (DTO) 【发布时间】:2012-12-05 16:27:25 【问题描述】:

很多时候,我发现自己在编写一些琐碎的数据类型,比如

class Pruefer:
    def __init__(self, ident, maxNum=float('inf'), name=""):
        self.ident  = ident
        self.maxNum = maxNum
        self.name   = name

虽然这非常有用(显然我不想用匿名 3 元组替换上面的内容),但它也是非常样板的。

现在,例如,当我想在字典中使用该类时,我必须添加更多样板,例如

    def __hash__(self):
        return hash(self.ident, self.maxNum, self.name)

我承认在我的所有样板类中识别一般模式可能很困难,但是我想提出这个问题:

有没有 python 中的流行习语用命名访问器派生快速和肮脏的数据类型?

或者,如果没有,也许 Python 大师可能想炫耀一些元类黑客或类工厂以使我的生活更轻松?

【问题讨论】:

我认为namedtuple 已经足够好了(添加了代码示例的完整答案) namedtuple 现在允许 3.7+ 中的默认值 【参考方案1】:

python 中是否有任何流行的习惯用法可以通过命名访问器导出快速...数据类型?

Dataclases。他们完全满足了这一需求。

有些答案提到了数据类,但这里是一个例子。

代码

import dataclasses as dc


@dc.dataclass(unsafe_hash=True)
class Pruefer:
    ident : int
    maxnum : float = float("inf")
    name : str  = ""

演示

pr = Pruefer(1, 2.0, "3")

pr
# Pruefer(ident=1, maxnum=2.0, name='3')

pr.ident
# 1

pr.maxnum
# 2.0

pr.name
# '3'

hash(pr)
# -5655986875063568239

详情

你得到:

漂亮的代表 默认值 散列 点属性访问 ...更多

你没有(直接)得到:

元组解包(与 namedtuple 不同)

这里是guide,关于数据类的详细信息。

【讨论】:

【参考方案2】:

如果使用 Python 3.7,您可以使用数据类;数据类可以被认为是“具有默认值的可变命名元组”

https://docs.python.org/3/library/dataclasses.html

https://www.python.org/dev/peps/pep-0557/

【讨论】:

【参考方案3】:

Python 3.6 中最有前途的东西之一是变量注释。他们允许用下一种方式将 namedtuple 定义为类:

In [1]: from typing import NamedTuple

In [2]: class Pruefer(NamedTuple):
   ...:     ident: int
   ...:     max_num: int
   ...:     name: str
   ...:     

In [3]: Pruefer(1,4,"name")
Out[3]: Pruefer(ident=1, max_num=4, name='name')

它与命名元组相同,但保存注释并允许使用 mypy 等静态类型分析器检查类型。

更新:15.05.2018

现在,在 Python 3.7 中存在 dataclasses,因此这是定义 DTO 的首选方式,也为了向后兼容,您可以使用 attrs 库。

【讨论】:

【参考方案4】:

可以帮助您使样板代码更通用的另一种方法是对(本地)变量字典的迭代。这使您可以将变量放在列表中,并在循环中处理这些变量。例如:

class Pruefer:
     def __init__(self, ident, maxNum=float('inf'), name=""):
         for n in "ident maxNum name".split():
             v = locals()[n]  # extract value from local variables
             setattr(self, n, v)  # set member variable

     def printMemberVars(self):
         print("Member variables are:")
         for k,v in vars(self).items():
             print("  : ''".format(k, v))


P = Pruefer("Id", 100, "John")
P.printMemberVars()

给予:

Member Variables are:
  ident: 'Id'
  maxNum: '100'
  name: 'John'

从有效利用资源的角度来看,这种方法当然不是最理想的。

【讨论】:

【参考方案5】:

对于 Alexey Kachayev 已经非常出色的答案,我没有什么要补充的——但是,可能有用的一件事是以下模式:

Pruefer.__new__.func_defaults = (1,float('inf'),"")

这将允许您创建一个工厂函数,该函数返回一个可以具有默认参数的新命名元组:

def default_named_tuple(name,args,defaults=None):
    named_tuple = collections.namedtuple(name,args)
    if defaults is not None:
        named_tuple.__new__.func_defaults = defaults
    return named_tuple

这可能看起来像是黑魔法——起初它对我有用,但它都记录在 Data Model 中并在 this post 中讨论。

在行动:

>>> default_named_tuple("Pruefer", "ident maxNum name",(1,float('inf'),''))
<class '__main__.Pruefer'>
>>> Pruefer = default_named_tuple("Pruefer", "ident maxNum name",(1,float('inf'),''))
>>> Pruefer()
Pruefer(ident=1, maxNum=inf, name='')
>>> Pruefer(3)
Pruefer(ident=3, maxNum=inf, name='')
>>> Pruefer(3,10050)
Pruefer(ident=3, maxNum=10050, name='')
>>> Pruefer(3,10050,"cowhide")
Pruefer(ident=3, maxNum=10050, name='cowhide')
>>> Pruefer(maxNum=12)
Pruefer(ident=1, maxNum=12, name='')

并且只指定一些参数作为默认值:

>>> Pruefer = default_named_tuple("Pruefer", "ident maxNum name",(float('inf'),''))
>>> Pruefer(maxNum=12)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() takes at least 2 arguments (2 given)
>>> Pruefer(1,maxNum=12)
Pruefer(ident=1, maxNum=12, name='')

请注意,正如所写,将tuple 作为defaults 传递可能是安全的。但是,通过确保函数中有一个合理的 tuple 对象,您可以轻松获得更多花哨。

【讨论】:

【参考方案6】:
>>> from collections import namedtuple
>>> Pruefer = namedtuple("Pruefer", "ident maxNum name")
>>> pr = Pruefer(1,2,3)
>>> pr.ident
1
>>> pr.maxNum
2
>>> pr.name
3
>>> hash(pr)
2528502973977326415

要提供默认值,您需要做更多的事情......简单的解决方案是为__new__方法编写重新定义的子类:

>>> class Pruefer(namedtuple("Pruefer", "ident maxNum name")):
...     def __new__(cls, ident, maxNum=float('inf'), name=""):
...         return super(Pruefer, cls).__new__(cls, ident, maxNum, name)
... 
>>> Pruefer(1)
Pruefer(ident=1, maxNum=inf, name='')

【讨论】:

太好了!你是否也知道一些可以让我拥有默认值的东西? @JoSo -- 你可以有一个工厂函数,它有默认值并返回一个Pruefer 实例。 这可能只有我一个人,但我更喜欢("ident","maxNum","name"),而不是带有空格分隔字符串的版本......这对我来说似乎更明显。 @mgilson:文档说你也可以使用["a1","a2","a3"]。 (但我个人对较少墨水的“a1 a2 a3”很好。想想perlqw(a1 a2 a3);>) @Duncan -- 我个人喜欢在事后添加默认值:Pruefer.__new__.func_defaults=(1,float('inf'),"")(见我迟到的答案)

以上是关于Python:快速而肮脏的数据类型 (DTO)的主要内容,如果未能解决你的问题,请参考以下文章

ruby CarrierWave带有来自雾的备用URL,快速而肮脏。对于将生产数据提取到开发中非常有用,以防万一你必须全力以赴

css 快速而肮脏的根css,每次都有效

在配置了 s2Member 的 WordPress 中获取“失效”成员的快速而肮脏的方法是啥?

ruby 快速而肮脏的脚本,以PDF格式从AT&T获取最新账单。

python 快速和肮脏的查克诺里斯python脚本。用于我们的SVN post commit钩子。如果您没有请求库:yum install pytho

scss 对于zurb基金会问题#2789来说,这是一种快速而又肮脏的解决方案。它没有经过很好的测试,但我认为它可能会节省一些时间