如何使用namedtuple
namedtuple
是一种带名称的tuple
,你只需要引入它就可以使用了
from collections import namedtuple
例如:
>>>from collections import namedtuple
>>>Person = namedtuple('Person', ['name', 'age'])
>>>person = Person(name = 'zhangsan', age=18)
>>> print (person)
Person(name='zhangsan', age=18)
>>> len(person)
2
>>> person.name
'zhangsan'
>>> person['age']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: tuple indices must be integers or slices, not str
>>> person.age
18
>>> person[0]
'zhangsan'
>>> person[1]
18
>>> type(person)
<class '__main__.Person'>
>>> person.age = 18 #不能被修改
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> alter_person = person._replace(age=19) #可以通过_replace去修改
>>> alter_person
Person(name='zhangsan', age=19)
>>> person #原person未发生改变
Person(name='zhangsan', age=18)
>>>
通过这个例子可以反映出namedtuple
拥有tuple
的特性,有长度可以通过下标访问,但是它又是一个自定义class
,拥有自己的属性,但是属性不可修改,你只能通过_replace
函数生成一个修改后的副本。
好处
namedtuple最大的优点就是它消除了直接通过下标去访问tuple
,使代码更具可读性。来看看python的哲学吧^_^
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
好奇之处
-
namedtuple
如何通过一个字符串‘Person’,去构建了一个<class '__main__.Person'>
-
namedtuple
如何通过动态创建类型并绑定属性 -
namedtuple
如何保留tuple的特性
源码测试文件构建
named_tupple.py
import sys as _sys
from keyword import iskeyword as _iskeyword
from operator import itemgetter as _itemgetter, eq as _eq
import _collections_abc
try:
from _collections import deque
except ImportError:
pass
else:
_collections_abc.MutableSequence.register(deque)
try:
from _collections import defaultdict
except ImportError:
pass
try:
from _collections import OrderedDict
except ImportError:
# Leave the pure Python version in place.
pass
_nt_itemgetters = {}
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
"""Returns a new subclass of tuple with named fields.
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Point.__doc__ # docstring for the new class
'Point(x, y)'
>>> p = Point(11, y=22) # instantiate with positional args or keywords
>>> p[0] + p[1] # indexable like a plain tuple
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> d = p._asdict() # convert to a dictionary
>>> d['x']
11
>>> Point(**d) # convert from a dictionary
Point(x=11, y=22)
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)
"""
# Validate the field names. At the user's option, either generate an error
# message or automatically replace the field name with a valid name.
if isinstance(field_names, str):
field_names = field_names.replace(',', ' ').split()
field_names = list(map(str, field_names))
typename = _sys.intern(str(typename))
if rename:
seen = set()
for index, name in enumerate(field_names):
if (not name.isidentifier()
or _iskeyword(name)
or name.startswith('_')
or name in seen):
field_names[index] = f'_{index}'
seen.add(name)
for name in [typename] + field_names:
if type(name) is not str:
raise TypeError('Type names and field names must be strings')
if not name.isidentifier():
raise ValueError('Type names and field names must be valid '
f'identifiers: {name!r}')
if _iskeyword(name):
raise ValueError('Type names and field names cannot be a '
f'keyword: {name!r}')
seen = set()
for name in field_names:
if name.startswith('_') and not rename:
raise ValueError('Field names cannot start with an underscore: '
f'{name!r}')
if name in seen:
raise ValueError(f'Encountered duplicate field name: {name!r}')
seen.add(name)
field_defaults = {}
if defaults is not None:
defaults = tuple(defaults)
if len(defaults) > len(field_names):
raise TypeError('Got more default values than field names')
field_defaults = dict(reversed(list(zip(reversed(field_names),
reversed(defaults)))))
# Variables used in the methods and docstrings
# ('name', 'shares', 'price', 'date', 'time')
field_names = tuple(map(_sys.intern, field_names))
num_fields = len(field_names)
# 'name, shares, price, date, time'
arg_list = repr(field_names).replace("'", "")[1:-1]
# '(name=%r, shares=%r, price=%r, date=%r, time=%r)'
repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')'
tuple_new = tuple.__new__
_len = len
# Create all the named tuple methods to be added to the class namespace
# 'def __new__(_cls, name, shares, price, date, time): return _tuple_new(_cls, (name, shares, price, date, time))'
s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))'
# {'_tuple_new': <built-in method __new__ of type object at 0x00007FFBBEC96A10>, '__name__': 'namedtuple_Stock'}
namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'}
# Note: exec() has the side-effect of interning the field names
# 动态生成类namedtuple_Stock,并且提供了__init__方法
exec(s, namespace)
# 指向 class namedtuple_Stock 构造方法
__new__ = namespace['__new__']
__new__.__doc__ = f'Create new instance of {typename}({arg_list})'
if defaults is not None:
__new__.__defaults__ = defaults
@classmethod
def _make(cls, iterable):
result = tuple_new(cls, iterable)
if _len(result) != num_fields:
raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
return result
_make.__func__.__doc__ = (f'Make a new {typename} object from a sequence '
'or iterable')
def _replace(_self, **kwds):
result = _self._make(map(kwds.pop, field_names, _self))
if kwds:
raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
return result
_replace.__doc__ = (f'Return a new {typename} object replacing specified '
'fields with new values')
def __repr__(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + repr_fmt % self
def _asdict(self):
'Return a new OrderedDict which maps field names to their values.'
return OrderedDict(zip(self._fields, self))
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return tuple(self)
# Modify function metadata to help with introspection and debugging
for method in (__new__, _make.__func__, _replace,
__repr__, _asdict, __getnewargs__):
method.__qualname__ = f'{typename}.{method.__name__}'
# Build-up the class namespace dictionary
# and use type() to build the result class
# {'__doc__': 'Stock(name, shares, price, date, time)', '__slots__': (), '_fields': ('name', 'shares', 'price', 'date', 'time'), '_fields_defaults': {}, '__new__': <function Stock.__new__ at 0x000001AE40BC1D08>, '_make': <classmethod object at 0x000001AE40C302E8>, '_replace': <function Stock._replace at 0x000001AE40BC1F28>, '__repr__': <function Stock.__repr__ at 0x000001AE40BC1C80>, '_asdict': <function Stock._asdict at 0x000001AE40BC1EA0>, '__getnewargs__': <function Stock.__getnewargs__ at 0x000001AE40C67048>}
# 这里自定义了类
class_namespace = {
'__doc__': f'{typename}({arg_list})',
'__slots__': (),
'_fields': field_names,
'_fields_defaults': field_defaults,
'__new__': __new__,
'_make': _make,
'_replace': _replace,
'__repr__': __repr__,
'_asdict': _asdict,
'__getnewargs__': __getnewargs__,
}
cache = _nt_itemgetters
for index, name in enumerate(field_names):
try:
itemgetter_object, doc = cache[index]
except KeyError:
itemgetter_object = _itemgetter(index)
doc = f'Alias for field number {index}'
cache[index] = itemgetter_object, doc
# 动态定义属性,并且将属性的获取委托给了operator.itemgetter函数
class_namespace[name] = property(itemgetter_object, doc=doc)
# 声明 class Stock
result = type(typename, (tuple,), class_namespace)
# For pickling to work, the __module__ variable needs to be set to the frame
# where the named tuple is created. Bypass this step in environments where
# sys._getframe is not defined (Jython for example) or sys._getframe is not
# defined for arguments greater than 0 (IronPython), or where the user has
# specified a particular module.
if module is None:
try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
if module is not None:
result.__module__ = module
return result
Stock = namedtuple('Stock', ['name','shares', 'price', 'date', 'time'])
s = Stock('', 0, 0.0, None, None)
s = s._replace(name = 'aaa')
print(s)
我们学到了什么
-
namedtuple
的field_names
可以是一个list
也可以是一个字符串name,age
因为
...
if isinstance(field_names, str):
field_names = field_names.replace(',', ' ').split()
...
- python 中通过
f'{name}'
语法可以引用上下文中的变量
会去处理如果想添加单引号可以添加!r
,例如:
>>> name = 'test'
>>> print(f'{name}',f'{name!r}' )
test 'test'
>>>
- 动态声明一个类
type() With name, bases and dict Parameters
If three parameters are passed to type(), it returns a new type object.
The three parameters are:
Parameter Description
name a class name; becomes the name attribute
bases a tuple that itemizes the base class; becomes the bases attribute
dict a dictionary which is the namespace containing definitions for the class body; becomes the dict attribute
https://www.programiz.com/python-programming/methods/built-in/type
https://docs.python.org/zh-cn/3.8/library/types.html
总结
namedtuple
创建过程其实就是一个通过type
函数自定义类型的过程,并且将父类指向tuple
,因此它才有了tuple
的一些特性,并且它将属性的获取委托给了operator.itemgetter
函数
网友评论