Python之namedtuple源码分析

Posted Fate0729

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python之namedtuple源码分析相关的知识,希望对你有一定的参考价值。

namedtuple()函数根据提供的参数创建一个新类,这个类会有一个类名,一些字段名和一个可选的用于定义类行为的关键字,具体实现如下

namedtuple函数源码

from keyword import iskeyword as _iskeyword
import sys as _sys


import logging
logging.basicConfig(level=logging.INFO, filename="logging.txt", filemode="w+",                     format = %(asctime)s - %(name)s - %(levelname)s - %(message)s)
logger = logging.getLogger(__name__)

_class_template = """from builtins import property as _property, tuple as _tuple
from operator import itemgetter as _itemgetter
from collections import OrderedDict

class {typename}(tuple):
    ‘{typename}({arg_list})‘

    __slots__ = ()

    _fields = {field_names!r}

    def __new__(_cls, {arg_list}):
        ‘Create new instance of {typename}({arg_list})‘
        return _tuple.__new__(_cls, ({arg_list}))

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        ‘Make a new {typename} object from a sequence or iterable‘
        result = new(cls, iterable)
        if len(result) != {num_fields:d}:
            raise TypeError(‘Expected {num_fields:d} arguments, got %d‘ % len(result))
        return result

    def _replace(_self, **kwds):
        ‘Return a new {typename} object replacing specified fields with new values‘
        result = _self._make(map(kwds.pop, {field_names!r}, _self))
        if kwds:
            raise ValueError(‘Got unexpected field names: %r‘ % list(kwds))
        return result

    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)

{field_defs}
"""

_repr_template = {name}=%r

_field_template = ‘‘‘    {name} = _property(_itemgetter({index:d}), doc=‘Alias for field number {index:d}‘)
‘‘‘

def namedtuple(typename, field_names, *, verbose=False, rename=False, 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 = str(typename)
    logging.info("%s:   %s" %(typename, field_names))
    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] = _%d % index
            seen.add(name)
    for name in [typename] + field_names:
        logging.info(name)
        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 
                             identifiers: %r % name)
        #判断是否为关键字,关键字为python内部已经使用了的标识符                     
        if _iskeyword(name):
            raise ValueError(Type names and field names cannot be a 
                             keyword: %r % name)
    seen = set()
    for name in field_names:
        if name.startswith(_) and not rename:
            raise ValueError(Field names cannot start with an underscore: 
                             %r % name)
        if name in seen:
            raise ValueError(Encountered duplicate field name: %r % name)
        seen.add(name)

    # Fill-in the class template
    class_definition = _class_template.format(
        typename = typename,
        field_names = tuple(field_names),
        num_fields = len(field_names),
        arg_list = repr(tuple(field_names)).replace("", "")[1:-1],
        repr_fmt = , .join(_repr_template.format(name=name)
                             for name in field_names),
        field_defs = \n.join(_field_template.format(index=index, name=name)
                               for index, name in enumerate(field_names))                       
    )
    logging.info(class_definition)

    # Execute the template string in a temporary namespace and support
    # tracing utilities by setting a value for frame.f_globals[‘__name__‘]
    namespace = dict(__name__=namedtuple_%s % typename)
    exec(class_definition, namespace)
    result = namespace[typename]
    result._source = class_definition
    if verbose:
        print(result._source)

    # 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

  通过函数模板字符串_class_template.format()会生成我们需要的实例类:

  eg: people = namedtuple("person","name,age,sex") 

class person(tuple) 分析

#coding=utf-8

from builtins import property as _property, tuple as _tuple
from operator import itemgetter as _itemgetter
from collections import OrderedDict

import logging
logging.basicConfig(level=logging.INFO, filename="logging.txt", filemode="w+",                     format = %(asctime)s - %(name)s - %(levelname)s - %(message)s)
logger = logging.getLogger(__name__)

class person(tuple):
    person(name, age, sex)

    __slots__ = ()

    _fields = (name, age, sex)

    def __new__(_cls, name, age, sex):
        Create new instance of person(name, age, sex)
        logger.info("__new__")
        return _tuple.__new__(_cls, (name, age, sex))

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        Make a new person object from a sequence or iterable
        result = new(cls, iterable)
        if len(result) != 3:
            raise TypeError(Expected 3 arguments, got %d % len(result))
        return result

    def _replace(_self, **kwds):
        Return a new person object replacing specified fields with new values

        #需要深刻理解该代码的精髓
        logger.info(type(_self))
        for item in _self:
            logger.info(item)
        str = ‘‘‘
            how to replace dict_keyvalue
            li = map({"age":99}.pop, (‘name‘, ‘age‘, ‘sex‘), ("zhanglin", "11", "man"))
        ‘‘‘
        logger.info(str)
        result = _self._make(map(kwds.pop, (name, age, sex), _self))
        if kwds:
            raise ValueError(Got unexpected field names: %r % list(kwds))
        return result
        
    def __repr__(self):
        Return a nicely formatted representation string
        return self.__class__.__name__ + (name=%r, age=%r, sex=%r) % 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)

    name = _property(_itemgetter(0), doc=Alias for field number 0)

    age = _property(_itemgetter(1), doc=Alias for field number 1)

    sex = _property(_itemgetter(2), doc=Alias for field number 2)

if __name__ == "__main__":
 
    p1 = person("zhanglin", "30", "man")
    logger.info("{0}:{1}".format("p1", p1))
    #_replace验证   
    p2 = p1._replace(name ="zhangsan", age=99)
    logger.info(p2)
    logger.info("{0}:{1}".format("p2", p2))
    

测试结果:

2018-03-21 15:10:46,197 - __main__ - INFO - __new__
2018-03-21 15:10:46,197 - __main__ - INFO - p1:person(name=zhanglin, age=30, sex=man)
2018-03-21 15:10:46,197 - __main__ - INFO - <class __main__.person>
2018-03-21 15:10:46,197 - __main__ - INFO - zhanglin
2018-03-21 15:10:46,197 - __main__ - INFO - 30
2018-03-21 15:10:46,197 - __main__ - INFO - man
2018-03-21 15:10:46,197 - __main__ - INFO - 
            how to replace dict_keyvalue
            li = map({"age":99}.pop, (name, age, sex), ("zhanglin", "11", "man"))
        
2018-03-21 15:10:46,197 - __main__ - INFO - person(name=zhangsan, age=99, sex=man)
2018-03-21 15:10:46,197 - __main__ - INFO - p2:person(name=zhangsan, age=99, sex=man)

 

以上是关于Python之namedtuple源码分析的主要内容,如果未能解决你的问题,请参考以下文章

namedtuple工厂函数,创造一个像实例对象的元祖(感觉到了Python的奇妙与可爱之处)。

python高级用法之命名元组namedtuple

(18)Blender源码分析之闪屏窗口的菜单python代码属性识别

(18)Blender源码分析之闪屏窗口的菜单python代码属性识别

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段

(24)Blender源码分析之顶层菜单显示代码分析