仅使用传递的参数的子集创建namedtuple对象

问题描述 投票:2回答:3

我使用以下方法从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 namedtuple定义所期望的名称相对应。

[我的第一个想法是尝试编写一个可以应用于每个类的装饰器,该装饰器将检查该类以查看其期望的字段,并仅将适当的参数传递给新对象。但是过去几天我才刚刚开始阅读有关装饰的文章,对此我还没有那么自信。

所以我的问题分为两部分:

  1. 这可能与单个装饰器有关,它将确定要装饰的特定类需要哪些字段?
  2. 是否存在具有相同功能的替代品,它会更易于使用,修改和理解?

我有太多潜在的表和字段排列,每个结果集中有数百万行,因此仅编写一个通用的namedtuple子类来处理每个不同的任务。查询时间和可用内存已被证明是限制因素。

[如果需要:

>>> sys.version
'2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]'
python arguments decorator namedtuple
3个回答
3
投票

首先,您必须重写__new__才能自定义namedtuple创建,因为namedtuple__new__方法甚至在到达__init__之前都会检查其参数。

第二,如果您的目标是接受和过滤关键字参数,则需要获取**kwargs并进行过滤并将其传递,而不仅仅是*args

因此,将其放在一起:

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替换该dict理解,但是每次我使用带有多个键的itemgetter时,没人知道它的含义,所以我很不情愿地停止使用它。


如果有必要,您也可以覆盖__init__,因为一旦__new__返回Foo实例,它将立即被调用。

但是您并不需要仅仅这样做,因为namedtuple的__init__不会接受任何参数或执行任何操作;值已在__new__中设置(与tuple和其他不可变类型一样)。看起来在CPython 2.7中,实际上是[[can super(Foo, self).__init__(*args, **kwargs),它将被忽略,但是在PyPy 1.9和CPython 3.3中,您会遇到TypeError。无论如何,没有理由通过它们,也没有人说它应该起作用,因此即使在CPython 2.7中也不要这样做。

请注意,您__init__将得到未过滤的kwargs。如果要更改此设置,可以在kwargs中原位更改__new__,而不是创建新词典。但是我相信仍然不能保证做任何事情;它只是使实现的定义是您获得过滤的args还是未过滤的,而不是保证未过滤的。


所以,你能把这个包起来吗?当然!

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作为参数。

此外,正如我们在评论中所建议的那样,我添加了一条线以抛弃所有多余的位置参数。


现在您就像使用namedtuple一样使用它,它会自动忽略任何多余的参数:

class Foo(LenientNamedTuple('Foo', ['id', 'name', 'age'])): pass print(Foo(id=1, name=2, age=3, spam=4))

打印(Foo(1,2,3,4,5))print(Foo(1,age = 3,name = 2,egg = 4))


我已经上传了a test,在genexpr上将dict理解替换为dict()以实现2.6兼容性(2.6是具有namedtuple的最早版本),但没有args被截断。在CPython 2.6.7、2.7.2、2.7.5、3.2.3、3.3.0和3.3.1,PyPy 1.9.0中,它可以与位置,关键字和混合参数(包括乱序关键字)一起使用和2.0b1,以及Jython 2.7b。

3
投票
namedtuple类型具有属性_fields,它是对象中字段名称的元组。您可以使用它来从数据库记录中挖掘必填字段。

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')
© www.soinside.com 2019 - 2024. All rights reserved.