• 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 = '
    '.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')

    根据类的字符串模板创建类

    #coding=utf-8
    
    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 = 
    '''
    class {typename}(tuple):
        __slots__ = ()
        _fields = {field_names!r}
        def __new__(cls, *args, **dwargs):
            return super().__new__(cls, *args, **dwargs)
    '''
    
    def defineclass(typename, field_names):
        logging.info(field_names) 
        class_definition = _class_template.format(typename=typename,field_names=field_names,
                #方法1
                arg_list = ','.join(name for name in field_names),
                self_format = "".join("		self.{name}={name}
    ".format(name=name) for name in field_names))
                #方法2
                #arg_list = ', '.join('{name}'.format(name=name) for name in field_names))
                 #方法3
                #arg_list = repr(tuple(field_names)).replace("'", "")[1:-1])
        logging.info(class_definition)
    
        namespace = dict(__name__='defineclass_%s' % typename)
        logging.info("namespce:{}".format(namespace))
        exec(class_definition, namespace)
        
        result = namespace[typename]
        logging.info("typename:{0}--result:{1}".format(typename, namespace[typename]))
        result._source = class_definition
    
        return result
            
    
    if __name__ == "__main__":
     
        person = defineclass("person", ("name", "age", "sex")) 
        print (type(person))
        p = person(("zhanglin", "31", "man"))
        print (p)
        print (p[0])
    2018-03-22 00:05:50,705 - root - INFO - ('name', 'age', 'sex')
    2018-03-22 00:05:50,705 - root - INFO - 
    class person(tuple):
        __slots__ = ()
        _fields = ('name', 'age', 'sex')
        def __new__(cls, *args, **dwargs):
            return super().__new__(cls, *args, **dwargs)
    
    2018-03-22 00:05:50,705 - root - INFO - namespce:{'__name__': 'defineclass_person'}
    2018-03-22 00:05:50,705 - root - INFO - typename:person--result:<class 'defineclass_person.person'>
  • 相关阅读:
    linux-指令
    rabbitmq启动
    [浪峰前端开发]JS获取当前时间戳的方法
    [浪峰JQuery开发]jquery最有意思的IFrame类似应用--值得深入研究
    [浪峰分享]移动电商:不是渠道拓展,而是一次重新创业
    [浪峰分享]App必死 Web永生 看Web的前世今生 必会卷土重来
    [浪峰分享]推荐一些不错的计算机书籍
    [浪峰转载]Jquery取得iframe中元素的几种方法
    [浪峰分享] 如何管理一个远程团队
    [浪峰分享] 博客园博客导航固顶--简单实用的css代码
  • 原文地址:https://www.cnblogs.com/xiaobingqianrui/p/8617272.html
Copyright © 2020-2023  润新知