《流畅的Python》第一章学习笔记

一摞python风格的纸牌

from collections import namedtuple

Card = namedtuple('Card', ['rank', 'suit'])  # 构建只有少数属性但是没有方法的对象


class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suites = '黑桃>红桃>方块>梅花'.split('>')

    def __init__(self):
        # 具名元祖实例化
        self._cards = [Card(rank, suit)
                       for suit in self.suites
                       for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, item):
        """
        可迭代
        把[]操作交给self._cards列表,支持切片操作
        :param item:
        :return:
        """
        # 具名元祖支持索引取值
        return self._cards[item] 


def spades_high(card):
    """
    排序
    2最小,A最大
    黑桃>红桃>方块>梅花
    :param card:
    :return:
    """
    suit_values = {
        "黑桃": 3,
        "红桃": 2,
        "方块": 1,
        "梅花": 0
    }
    rank_value = FrenchDeck.ranks.index(card.rank)
    return rank_value * len(suit_values) + suit_values[card.suit]


if __name__ == '__main__':
    c = Card(1, 2)
    print(c[1])
    f = FrenchDeck()
    print(len(f))  # 52 = (11 - 2 + 4) * 4
    print(f[0])  # Card(rank='2', suit='spades')
    print(f[:3])  # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
    for i in f:
        print(i)
    for i in reversed(f):
        print(i)
    for i in sorted(f, key=spades_high):
        print(i)

具名元祖

自 Python 2.6 开始,namedtuple 就加入到 Python 里,用以 构建只有少数属性但是没有方法的对象

在官方文档中也翻译为命名元祖

它赋予了每个位置一个含义,提供可读性自文档性

  • 通过名字获取值

  • 通过索引值获取值

导入方法

from collections import namedtuple

具名元祖源码阅读

  • 返回一个新的元组子类,名为 typename

test = namedtuple('mytest','test1 test2')
t = test(1, 2)
type(t) # __main__.mytest
  • field_names用空白或逗号分隔开元素名

if isinstance(field_names, str):
    field_names = field_names.replace(',', ' ').split()
field_names = list(map(str, field_names))
  • rename为真时, 无效字段名会自动转换成位置名

有效字段名:除了下划线开头的那些任何有效的Python 标识符。有效标识符由字母,数字,下划线组成,但首字母不能是数字或下划线,另外不能是关键词

比如 ['abc', 'def', 'ghi', 'abc'] 转换成 ['abc', '_1', 'ghi', '_3'] , 消除关键词 def 和重复字段名 abc

isidentifier:判断字符串是否是有效的 Python 标识符,可用来判断变量名是否合法

iskeyword:包含全部关键字的冻结的集合

from keyword import iskeyword as _iskeyword

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)
  • defaults:默认值

test = namedtuple('mytest', 'test1 test2', defaults=[1, 2])
test().test1 # 1
test().test2 # 2

test = namedtuple('mytest', 'test1 test2 test3', defaults=[1, 2])
test(0).test1 # 0
test(0).test2 # 1
test(0).test3 # 2
  • module:设置 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
test = namedtuple('mytest', 'test1 test2 test3', module='aaa')
type(test(1, 2, 3)) # aaa.mytest
  • _make:类方法从存在的序列或迭代实例创建一个新实例

@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
Point = namedtuple('Point', ['x', 'y'])
t = [11, 22]
Point._make(t)# Point(x=11, y=22)
  • _asdict:返回一个新的字典

def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))
p = Point(x=11, y=22)
p._asdict()# {'x': 11, 'y': 22}
  • _replace将指定域替换为新的值

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
p = Point(x=11, y=22)
p._replace(x=33) # Point(x=33, y=22)
  • _fields:列出字段名

p._fields #('x', 'y')
  • _field_defaults:字典将字段名称映射到默认值

Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
Account._field_defaults# {'balance': 0}
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
    field_names = tuple(map(_sys.intern, field_names)) # 转换为元祖
    num_fields = len(field_names)
    arg_list = repr(field_names).replace("'", "")[1:-1] 
    # repr(field_names):'(\'rank\', \'suit\')' 
    # .replace("'", "")[1:-1]:变为 'rank, suit'
    repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')' 
    # '(rank=%r, suit=%r)'
    tuple_new = tuple.__new__ 'rank, suit'
    _dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip

    # Create all the named tuple methods to be added to the class namespace

    s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))' # __new__的时候将内容变为元祖,后续访问的时候可以用索引来查找内容
    namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'}
    # Note: exec() has the side-effect of interning the field names
    exec(s, namespace)# 动态执行 Python 代码 补充namespace的__new__方法
    __new__ = namespace['__new__']
    __new__.__doc__ = f'Create new instance of {typename}({arg_list})' # 为__new__方法添加说明
    if defaults is not None:
        __new__.__defaults__ = defaults

    @classmethod
    def _make(cls, iterable): # 定义_make方法
        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')# 为_make添加说明

    def _replace(self, /, **kwds): # 定义_replace方法
        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')# 为_replace方法添加说明

    def __repr__(self):# 定义__repr__方法 在print的时候调用
        'Return a nicely formatted representation string'
        return self.__class__.__name__ + repr_fmt % self

    def _asdict(self): # 定义_asdict方法 转换成字典输出
        'Return a new dict which maps field names to their values.'
        return _dict(_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
    class_namespace = {
        '__doc__': f'{typename}({arg_list})',
        '__slots__': (),
        '_fields': field_names,
        '_field_defaults': field_defaults,
        # alternate spelling for backward compatibility
        '_fields_defaults': field_defaults,
        '__new__': __new__,
        '_make': _make,
        '_replace': _replace,
        '__repr__': __repr__,
        '_asdict': _asdict,
        '__getnewargs__': __getnewargs__,
    }
    for index, name in enumerate(field_names):
        doc = _sys.intern(f'Alias for field number {index}')
        class_namespace[name] = _tuplegetter(index, doc)# 为我们定义的属性字段赋值

    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:# 使用默认module名称
        try:
            module = _sys._getframe(1).f_globals.get('__name__', '__main__')
        except (AttributeError, ValueError):
            pass
    if module is not None:# 重新命名module名称
        result.__module__ = module

    return result

使用

Card = namedtuple('Card', ['rank', 'suit'])

在通过名字获取值的方式上类似于

class Card:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

列表推导式

  • [表达式 for 变量 in 列表]

  • [表达式 for 变量 in 列表 if 条件]

排序

sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

sort

In[1]:a = [3, 2, 1]
In[2]:a.sort()
In[3]:a
Out[3]:[1, 2, 3]

sorted

key 指定带有单个参数的函数,用于从 iterable 的每个元素中提取用于比较的键 (例如 key=str.lower)。默认值为 None (直接比较元素)。

In[1]:sorted([3, 2, 1])
Out[1]:[1, 2, 3]

魔方方法

__repr____str__

  • repr:在repr()方法中使用。当没有实现该方法的时候,打印实例可能为

  • str:在str()方法中使用,在print()方法中使用

当一个对象没有__str__的时候会调用__repr__

自己定义__bool__方法

如果没有定义的话,在执行bool(x)的时候默认会调用bool(x.__len__())

  • 9
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值