
本文介绍了Python的最佳发明之一:namedtuple。 我们将考虑其令人愉悦的功能,从知名到非显而易见。 对该主题的沉浸度将逐渐提高,因此我希望每个人都能为自己找到一些有趣的东西。 走吧
引言
当然,您面临的情况是您需要一次转移对象的多个属性。 例如,有关宠物的信息:类型,昵称和年龄。
通常,为这个事情创建一个单独的类太懒惰了,并且使用了元组:
("pigeon", "", 3) ("fox", "", 7) ("parrot", "", 1)
为了更清晰,一个命名元组-collections.namedtuple是合适的:
from collections import namedtuple Pet = namedtuple("Pet", "type name age") frank = Pet(type="pigeon", name="", age=3) >>> frank.age 3
每个人都知道this以下是一些鲜为人知的功能:
快速变更栏位
如果其中一项属性需要更改怎么办? 弗兰克(Frank)正在衰老,车队一成不变。 为了不完全重新创建它,我们提出了_replace()
方法:
>>> frank._replace(age=4) Pet(type='pigeon', name='', age=4)
如果要使整个结构可变_asdict()
:
>>> frank._asdict() OrderedDict([('type', 'pigeon'), ('name', ''), ('age', 3)])
自动标题更改
假设您从CSV导入数据并将每行变成一个元组。 字段名称取自CSV文件的标题。 但是出了点问题:
解决方案是在构造函数中rename=True
参数:
“不成功”的名称已根据序列号重命名。
预设值
如果一个元组有很多可选字段,那么每次创建对象时,您仍然必须列出它们:
Pet = namedtuple("Pet", "type name alt_name") >>> Pet("pigeon", "") TypeError: __new__() missing 1 required positional argument: 'alt_name' >>> Pet("pigeon", "", None) Pet(type='pigeon', name='', alt_name=None)
为了避免这种情况,请在构造函数中指定defaults
:
Pet = namedtuple("Pet", "type name alt_name", defaults=("",)) >>> Pet("pigeon", "") Pet(type='pigeon', name='', alt_name='')
defaults
从尾部分配默认值。 适用于python 3.7+
对于较旧的版本,您可以通过原型更笨拙地实现相同的结果:
Pet = namedtuple("Pet", "type name alt_name") default_pet = Pet(None, None, "") >>> default_pet._replace(type="pigeon", name="") Pet(type='pigeon', name='', alt_name='') >>> default_pet._replace(type="fox", name="") Pet(type='fox', name='', alt_name='')
但是,使用defaults
当然更好。
超轻
元组的优点之一就是轻便。 一支十万只鸽子的军队将仅占用10兆字节:
from collections import namedtuple import objsize
为了进行比较,如果将Pet设为普通类,则类似的列表将已经占用19兆字节。
发生这种情况是因为python中的普通对象带有重的__dict__ __dict__
,其中包含对象所有属性的名称和值:
class PetObj: def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_obj = PetObj(type="pigeon", name="", age=3) >>> frank_obj.__dict__ {'type': 'pigeon', 'name': '', 'age': 3}
namedtuple对象没有此字典,因此占用较少的内存:
frank = Pet(type="pigeon", name="", age=3) >>> frank.__dict__ AttributeError: 'Pet' object has no attribute '__dict__' >>> objsize.get_deep_size(frank_obj) 335 >>> objsize.get_deep_size(frank) 239
但是,命名元组如何摆脱__dict__
? 在ツ上阅读
丰富的内心世界
如果您已经使用python很长时间了,那么您可能知道:可以通过__slots__ dander创建一个轻量级的对象:
class PetSlots: __slots__ = ("type", "name", "age") def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_slots = PetSlots(type="pigeon", name="", age=3)
“插槽”对象没有具有属性的字典,因此它们占用的内存很少。 “插槽上的弗兰克”与“车队上的弗兰克”一样轻,请参阅:
>>> objsize.get_deep_size(frank) 239 >>> objsize.get_deep_size(frank_slots) 231
如果您决定namedtuple也使用插槽,这与事实相差不远。 您还记得,特定的元组类是动态声明的:
Pet = namedtuple("Pet", "type name age")
namedtuple构造函数使用不同的黑魔法,并生成类似此类的东西(大大简化):
class Pet(tuple): __slots__ = () type = property(operator.itemgetter(0)) name = property(operator.itemgetter(1)) age = property(operator.itemgetter(2)) def __new__(cls, type, name, age): return tuple.__new__(cls, (type, name, age))
也就是说,我们的Pet是一个普通的tuple
,三个属性方法已用钉子固定:
type
返回元组的null元素name
-元组的第一个元素age
-元组的第二个元素
仅使物体发光需要__slots__
。 因此,Pet占用的空间很小,可以用作常规元组:
>>> frank.index("") 1 >>> type, _, _ = frank >>> type 'pigeon'
狡猾地发明了吧?
不逊于数据类
由于我们在谈论代码生成。 在python 3.7中,出现了一个uber-generator代码,它没有相等的-数据类。
初次看到数据类时,您只想为此而切换到该语言的新版本:
from dataclasses import dataclass @dataclass class PetData: type: str name: str age: int
奇迹真好! 但有一个细微差别-它很胖:
frank_data = PetData(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_data) 335 >>> objsize.get_deep_size(frank) 239
数据类生成一个常规的python类,其对象在__dict__
下耗尽。 因此,如果您正在从基础读取线并将其转换为对象,则数据类不是最佳选择。
但是,等等,您可以像元组一样冻结数据类。 也许那样会变得容易吗?
@dataclass(frozen=True) class PetFrozen: type: str name: str age: int frank_frozen = PetFrozen(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_frozen) 335
las 即使冻结,它仍然是带有属性字典的普通重物。 因此,如果您需要不可变的轻型对象(也可以用作常规元组),则namedtuple仍然是最佳选择。
⌘
我真的很喜欢命名的元组:
并同时以150行代码实现。 幸福还需要什么ツ
如果您想进一步了解标准Python库,请订阅@ohmypy频道