昨晚在阅读 Flask-Principal 文档的时候发现了一段代码不是很懂什么意思,而且发现还很有趣,源代码是这样的:
from collections import namedtuple
from functools import partial
... ...
BlogPostNeed = namedtuple('blog_post', ['method', 'value'])
EditBlogPostNeed = partial(BlogPostNeed, 'edit')
... ...
need = EditBlogPostNeed(unicode(post_id))
中间省略了很多无关主题的代码,这里我觉得有意思的地方有两个:
- namedtuple('blog_post', ['method', 'value'])
- partial(BlogPostNeed, 'edit')
好似以前见过,但是一时居然说不上来什么意思,所以秉着好学的态度去 google (去年换工作之后就很少用x度了) 了一下。原来这两个函数这么有意思啊,partial 还好,我觉得更有意思的是 namedtuple。最先发现的资料是 python 的文档,毕竟是标准库中存在的函数,文档中的介绍是:
collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
Returns a new tuple subclass named typename. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable. Instances of the subclass also have a helpful docstring (with typename and field_names) and a helpful repr() method which lists the tuple contents in aname=value format.
翻译大意就是:
调用这个函数会返回一个命名为 'typename' 的元组子类。这个子类可以用来创建类似于元组的对象,这个对象可以索引访问,可以被迭代,也可以通过属性的方式访问;这还不够,对象还有一个有用的 docstring 和可以以 name=value 的形式列出元组内容的 repr 函数
哦,看完文档之后,发现原来就是一个快捷创建 POJO 的方式啊,很好,所以我马上就写了一段测试代码尝试了一下,结果就崩了
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'height', 'weight'])
person = Person()
运行一下,发现就报错了:
TypeError Traceback (most recent call last)
<ipython-input-5-f3379562d574> in <module>()
2
3 Person = namedtuple('Person', ['name', 'age', 'height', 'weight'])
----> 4 person = Person()
TypeError: __new__() takes exactly 5 arguments (1 given)
哦,原来初始化的时候一定要带满参数的,好,继续:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'height', 'weight'])
person = Person(name='tyrael', age=25, height=182, weight=68)
for value in person:
print value
for idx, value in enumerate(person):
print idx, value
print "Hello, {}, after waste {} years foods you only get {} cm tall and {} KG".format(
person.name, person.age, person.height, person.weight)
好,跑一下,好像没什么问题,继续探索,尝试改个名字:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'height', 'weight'])
person = Person(name='tyrael', age=25, height=182, weight=68)
person.name = 'yylucifer'
欧耶,又搞砸了,问题很快就抛出来了:
AttributeError Traceback (most recent call last)
<ipython-input-13-e2c8e303bb55> in <module>()
4 person = Person(name='tyrael', age=25, height=182, weight=68)
5
----> 6 person.name = 'yylucifer'
AttributeError: can't set attribute
看一下问题是不能设置属性,想一下,既然 namedtuple 是 tuple 的子类,那应该也像 tuple 一样一旦设值就不能修改了,很科学。
应用场景
好像没什么好玩了,于是就想了想,那这个 namedtuple 有什么用呢?除了用来快捷保存 key-value 之外。继续看完官网的文档,哇哦,不得了啊,人家给的场景简直了,用得非常得巧妙,将 map 和 namedtuple 结合起来之后,就可以实现非常 pythonic 的持久数据操作代码了。
先别说话,上个代码:
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
print emp.name, emp.title
import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
print emp.name, emp.title
这两处都是读取持久化数据(csv/db),然后转化成 namedtuple,然后再通过属性来访问,非常得简洁。
默认方法
除了从 tuple 上继承的所有方法,namedtuple 还有 3 个额外的方法以及 1 个额外的属性:
classmethod somenamedtuple._make(iterable)
使用已有的序列或者迭代构建新的对象。
>>>
>>> t = [11, 22]
>>> Point._make(t)
Point(x=11, y=22)
somenamedtuple._asdict()
返回一个包含 属性名称和对应值 的有序字典
>>>
>>> p = Point(x=11, y=22)
>>> p._asdict()
OrderedDict([('x', 11), ('y', 22)])
somenamedtuple._replace(kwargs)
修改指定属性的值,返回修改后的新对象。
>>>
>>> p = Point(x=11, y=22)
>>> p._replace(x=33)
Point(x=33, y=22)
>>> for partnum, record in inventory.items():
inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now())
somenamedtuple._fields
列举出所有属性名的字符串 tuple,在从已有的 namedtuple 创建新的 namedtuple 时特别有用。
>>> p._fields # view the field names
('x', 'y')
>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)