2

我正在从 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 个字段,并且只获取与namedtuple定义所期望的名称相对应的特定子集 M < N。

我的第一个想法是尝试编写一个可以应用于每个类的装饰器,它会检查该类以查看它所期望的字段,并仅将适当的参数传递给新对象。但是在过去的几天里,我刚刚开始阅读有关装饰器的内容,而且我对它们还没有那么自信。

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

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

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

如果需要的话:

>>> sys.version
'2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]'
4

3 回答 3

3

类型有一个namedtuple属性_fields,它是对象中字段名称的元组。您可以使用它从数据库记录中挖掘出所需的字段。

于 2013-07-12T19:22:01.130 回答
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)

您可以将 dict 理解替换为itemgetter,但是每次我将 itemgetter 与多个键一起使用时,没有人理解它的含义,所以我很不情愿地停止使用它。


__init__如果您有理由这样做,您也可以覆盖,因为它会在__new__返回Foo实例时立即被调用。

但是你不需要仅仅为了这个,因为 namedtuple__init__不接受任何参数或做任何事情;这些值已经被设置__new__(就像tuple, 和其他不可变类型一样)。看起来在 CPython 2.7 中,你实际上可以 super(Foo, self).__init__(*args, **kwargs)并且它会被忽略,但是在 PyPy 1.9 和 CPython 3.3 中,你会得到一个 TypeError。无论如何,没有理由通过它们,也没有说它应该起作用,所以即使在 CPython 2.7 中也不要这样做。

请注意,您__init__将获得未过滤的kwargs. 如果你想改变它,你可以kwargs就地改变 in __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其作为参数。

此外,正如评论中所建议的那样,当我们这样做时,我添加了一行来丢弃任何多余的位置参数。


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

class Foo(LenientNamedTuple('Foo', ['id', 'name', 'age'])):
    pass

print(Foo(id=1, name=2, age=3, spam=4))

    print(Foo(1, 2, 3, 4, 5)) print(Foo(1, age=3, name=2, Eggs=4))


我上传了一个 test,将 dict comprehension 替换为dict()on agenexpr 以实现 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。

于 2013-07-12T19:34:00.020 回答
2

这些答案似乎都过于复杂。您真的想要新的类和重载,而不是仅仅编写一行代码或一个辅助函数来以您想要的方式实例化标准数据类型吗?

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')

达达!

或者做一个小帮手。

# 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')
于 2019-12-12T04:37:16.940 回答