我使用以下方法从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
子类来处理每个不同的任务。查询时间和可用内存已被证明是限制因素。
[如果需要:
>>> sys.version
'2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]'
首先,您必须重写__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。
namedtuple
类型具有属性_fields
,它是对象中字段名称的元组。您可以使用它来从数据库记录中挖掘必填字段。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')