我使用以下方法将MySQL数据库中的行作为字典(使用SSDictCursor)并进行一些处理:
from collections import namedtuple
class Foo(namedtuple('Foo', ['id', 'name', 'age'])):
__slots__ = ()
def __init__(self, *args):
super(Foo, self).__init__(self, *args)
# ...some class methods below here
class Bar(namedtuple('Bar', ['id', 'address', 'city', 'state']):
__slots__ = ()
def __init__(self, *args):
super(Bar, self).__init__(self, *args)
# some class methods here...
# more classes for distinct processing tasks...
要使用namedtuple
,我必须事先知道我想要的字段,这很好。但是,我想允许用户将一个简单的SELECT *
语句提供给我的程序,然后迭代结果集的行,使用这些不同的类执行多个任务。为了使这项工作,我的类必须以某种方式检查从光标进入的N个字段并仅采用特定的子集M< N对应于namedtuple
定义所期望的名称。
我的第一个想法是尝试编写一个我可以应用于每个类的装饰器,它将检查类以查看它所期望的字段,并仅将适当的参数传递给新对象。但是我刚刚开始阅读过去几天关于装饰器的内容,而且我对它们并不那么自信。
所以我的问题分为两部分:
我有太多表和字段的潜在排列,每个结果集中有数百万行,只需编写一个通用namedtuple
子类来处理每个不同的任务。查询时间和可用内存已被证明是限制因素。
如果需要:
>>> sys.version
'2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]'
答案 0 :(得分:3)
namedtuple
类型具有属性_fields
,该属性是对象中字段名称的元组。您可以使用它从数据库记录中挖掘出必需的字段。
答案 1 :(得分:2)
首先,您必须覆盖__new__
才能自定义namedtuple
创建,因为namedtuple
的{{1}}方法会在您到达{{{{}}之前检查其参数1}}。
其次,如果您的目标是接受并过滤关键字参数,则需要__new__
并过滤并传递相关信息,而不仅仅是__init__
。
所以,把它放在一起:
**kwargs
你可以用*args
替换那个dict理解,但每次我使用带有多个键的itemgetter时,没有人理解它的意思,所以我不情愿地停止使用它。
如果您有理由,也可以覆盖class Foo(namedtuple('Foo', ['id', 'name', 'age'])):
__slots__ = ()
def __new__(cls, *args, **kwargs):
kwargs = {k: v for k, v in kwargs.items() if k in cls._fields}
return super(Foo, cls).__new__(cls, *args, **kwargs)
,因为只要itemgetter
返回__init__
个实例,就会调用它。
但你不需要这样做,因为namedtuple的__new__
不接受任何争论或做任何事情;值已在Foo
中设置(与__init__
一样,以及其他不可变类型)。看起来CPython 2.7,你实际上可以 __new__
并且它只会被忽略,但是使用PyPy 1.9和CPython 3.3,你会得到一个TypeError。无论如何,没有理由通过它们,没有任何说它应该有效,所以即使在CPython 2.7中也不要这样做。
请注意,您tuple
将获得未经过滤的super(Foo, self).__init__(*args, **kwargs)
。如果您想更改它,可以在__init__
中就地改变kwargs
,而不是制作新词典。但我相信仍然无法保证做任何事情;它只是让它实现定义你是否得到过滤的args或未过滤的args,而不是保证未过滤的。
那么,你可以把它包起来吗?当然!
kwargs
请注意,这样做的好处是不必使用准私有/半文档__new__
类属性,因为我们已经将def LenientNamedTuple(name, fields):
class Wrapper(namedtuple(name, fields)):
__slots__ = ()
def __new__(cls, *args, **kwargs):
args = args[:len(fields)]
kwargs = {k: v for k, v in kwargs.items() if k in fields}
return super(Wrapper, cls).__new__(cls, *args, **kwargs)
return Wrapper
作为参数。
此外,正如我们所说的那样,我添加了一行来抛弃任何多余的位置参数,如评论中所示。
现在你只需使用_fields
,它会自动忽略任何多余的参数:
fields
打印(Foo(1,2,3,4,5)) 打印(Foo(1,年龄= 3,名称= 2,鸡蛋= 4))
我上传了a test,在genexpr上用namedtuple
替换了dict理解,以获得2.6兼容性(2.6是class Foo(LenientNamedTuple('Foo', ['id', 'name', 'age'])):
pass
print(Foo(id=1, name=2, age=3, spam=4))
最早的版本),但没有args截断。它适用于CPython 2.6.7,2.7.2,2.7.5,3.2.3,3.3.0和3.3.1,PyPy 1.9.0中的位置,关键字和混合args,包括无序关键字。和2.0b1,以及Jython 2.7b。
答案 2 :(得分:0)
这些答案似乎都过于复杂。您是否真的想要新的类和重载,而不是仅仅编写代码或帮助函数以所需的方式实例化标准数据类型?
Foo = namedtuple('Foo', ['id', 'name', 'age'], defaults=(None,) * 3)
Bar = namedtuple('Bar', ['id', 'address', 'city', 'state'], defaults=(None,) * 4)
poo = {'id': 1, 'age': 'Orz', 'city': 'Tucson', 'weight': True}
ooh = {'id': 2, 'name': 'Spathi', 'state': 'Iowa', 'children': '25'}
>>> Foo(*[poo[f] if f in poo else None for f in Foo._fields])
Foo(id=1, name=None, age='Orz')
Ta-daa!
或者做一个小帮手。
# nt should have defaults
def nt_from_kwargs(nt, **kwargs):
return nt(**dict(i for i in kwargs.items() if i[0] in nt._fields))
>>> nt_from_kwargs(Foo, id=1, age='Orz', city='Tucson', weight=True)
Foo(id=1, name=None, age='Orz')
>>> nt_from_kwargs(Bar, **poo)
Bar(id=1, address=None, city='Tucson', state=None)
>>> nt_from_kwargs(Bar, **{**poo, **ooh})
Bar(id=2, address=None, city='Tucson', state='Iowa')
每个人都喜欢字典。
def nt_from_dict(nt, d):
return nt(*[d[k] if k in d else None for k in nt._fields])
>>> nt_from_dict(Foo, poo)
Foo(id=1, name=None, age='Orz')
>>> nt_from_dict(Bar, poo)
Bar(id=1, address=None, city='Tucson', state=None)
>>> nt_from_dict(Bar, {**poo, **ooh})
Bar(id=2, address=None, city='Tucson', state='Iowa')