zoukankan      html  css  js  c++  java
  • 流畅的python——1 数据模型

    一、数据模型

    当python解释器遇到特殊的句法时,会调用特殊方法,比如:d['a'] 会调用 __getitem__

    magic 和 dunder:魔法方法 magic method是特殊方法的昵称。特殊方法也叫 双下方法 dunder method

    具名元组 namedtuple:用于构建只有少数属性,但是,没有方法的对象。

    import collections
    
    Card = collections.namedtuple('Card',['rank','suit'])
    

    特殊方法:__len____getitem__

    class FrenchDeck:
         ranks = [str(n) for n in range(2, 11)] + list('JQKA')
         suits = 'spades diamonds clubs hearts'.split()
         def __init__(self):
             self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
         def __len__(self):  # 调用 len() ,触发 __len__ 特殊方法
         	return len(self._cards)
         def __getitem__(self, position):  # 调用 f[0] ,触发 __getitem__ 方法
         	return self._cards[position]
    
    In [6]: f = F()
    
    In [8]: len(f)
    Out[8]: 52
    
    In [9]: f[0]
    Out[9]: Card(rank='2', suit='spades')
    

    random.choice 从序列中随机取一个元素,注意:此时的 f 是一个对象

    In [11]: random.choice(f)
    Out[11]: Card(rank='10', suit='spades')
    
    In [12]: f
    Out[12]: <__main__.F at 0x2a3d7ee3588>
        
    In [13]: for i in f:
        ...:     print(i)
        ...:
    Card(rank='2', suit='spades')
    Card(rank='3', suit='spades')
    Card(rank='4', suit='spades')
    Card(rank='5', suit='spades')
    Card(rank='6', suit='spades')
    Card(rank='7', suit='spades')
    Card(rank='8', suit='spades')
    Card(rank='9', suit='spades')
    Card(rank='10', suit='spades')
    ...
    

    因为 __getitem__方法把 [] 操作交给了 self._card 列表,所以,f 自动支持切片操作,即可迭代,迭代 f 对象,得到的结果是迭代 self._card 列表。

    __getitem__ 实现该特殊方法:[] 和 可迭代

    迭代是隐式的,如果一个集合类型,没有实现 __contains__ 方法,in 运算符就会按顺序做一次迭代搜索。

    sorted 排序

    In [20]: def sort_f(card):
        ...:     rank_value = F.ranks.index(card.rank)
        ...:     suit_dict = dict(spades=3,hearts=2,diamonds=1,clubs=0)
        ...:     return rank_value * len(suit_dict) + suit_dict[card.suit]
        ...:
    
    In [21]: sorted(f,key=sort_f)
    

    虽然 FrenchDeck 隐式地继承了 object 类,5 但功能却不是继承而来的。我们通过数据模型和一些合成来实现这些功能。通过实现__len____getitem__这两个特殊方法,FrenchDeck 就跟一个 Python 自有的序列数据类型一样,可以体现出 Python 的核心语言特性(例如迭代和切片)。同时这个类还可以用于标准库中诸如 random.choice、reversed 和 sorted 这些函数。另外,对合成的运用使得__len____getitem__ 的具体实现可以代理给 self._cards 这个 Python 列表(即 list 对象)。

    特殊方法的存在是为了被python解释器调用的,不是我们自己来调用。比如,没有obj.__len()这种写法,应该使用len(obj) 。是执行 len 方法,然后,调用了 __len__ 方法。

    对于python内置类型,__len__ 会返回 PyVarObject 的 ob_size 属性,PyVarObject 是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用一个方法要快的多。

    for i in x: 对应的函数是 iter(x) ,调用的是 x.__iter__() 方法。

    不要自己想当然地随意添加特殊方法,比如 foo 之类的,因为虽然现在这个名字没有被 Python 内部使用,以后就不一定了。

    实现一个简单的向量类:

    In [38]: class Vector:
        ...:     def __init__(self,x,y):
        ...:         self.x = x
        ...:         self.y = y
        ...:     def __abs__(self):  # abs方法
        ...:         return hypot(self.x,self.y)
        ...:     def __add__(self,other):  # + 运算符,中缀运算符
        ...:         return Vector(self.x + other.x,self.y + other.y)
        ...:     def __repr__(self):  # repr方法,终端打印
        ...:         return '({},{})'.format(self.x,self.y)
        ...:     def __bool__(self):  # bool方法
        ...:         return bool(abs(self))
        ...:     def __mul__(self,scalar):  # * 运算符,中缀运算符
        ...:         return Vector(self.x * scalar,self.y * scalar)
    

    repr方法

    Python 有一个内置的函数叫 repr,它能把一个对象用字符串的形式表达出来以便辨认,这就是“字符串表示形式”。

    交互式控制台和调试程序(debugger)用 repr 函数来获取字符串表示形式;

    在老的使用% 符号的字符串格式中,这个函数返回的结果用来代替 %r 所代表的对象;同样,str.format 函数所用到的新式字符串格式化语法

    __repr____str__ 的区别:__str__str() 函数使用时,被调用;或是在 print 函数调用

    两个方法,__repr__更好一点,因为如果一个对象没有 __str__ 函数,而需要调用它时,解释器会用__repr__作为替代而调用。

    __bool__ 方法

    尽管 Python 里有 bool 类型,但实际上任何对象都可以用于需要布尔值的上下文中(比如if 或 while 语句,或者 and、or 和 not 运算符)。为了判定一个值 x 为真还是为假,Python 会调用 bool(x),这个函数只能返回 True 或者 False。

    默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对 __bool__ 或者__len__ 函数有自己的实现。bool(x) 的背后是调用 x.__bool__() 的结果;如果不存在 __bool__ 方法,那么 bool(x) 会尝试调用 x.__len__()。若返回 0,则 bool 会返回False;否则返回 True。

    In [49]: a = Vector(0,0)
    
    In [50]: a
    Out[50]: (0,0)
    
    In [51]: if a:
        ...:     print('kkk')
        ...:
    
    In [52]:
    

    如果想让 Vector.__bool__ 更高效,可以采用这种实现:

    def __bool__(self):
        return bool(self.x or self.y)
    

    它不那么易读,却能省掉从 abs 到__abs__到平方再到平方根这些中间步骤。通过bool 把返回类型显式转换为布尔值是为了符合__bool__对返回值的规定,因为 or运算符可能会返回 x 或者 y 本身的值:若 x 的值等价于真,则 or 返回 x 的值;否则返回 y 的值。

    为什么 len 不是普通方法:因为 对于内置类型 len方法 是直接读取属性,获取长度。

    通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。

    数据模型和对象模型;魔术方法与特殊方法;元对象协议=对象模型:构建核心语言的API

  • 相关阅读:
    MMORPG大型游戏设计与开发(客户端架构 part14 of vegine)
    java线程与并发(一)
    HTTP学习笔记(五)
    http学习笔记(四)——HTTP报文
    http学习笔记(三)
    http学习笔记(二)—— 嘿!伙计,你在哪?(URL)
    http学习笔记(一)
    本地DNS安装
    SQL Server求解连续操作(登录)数量(次数)最大的记录(用户)问题
    一个有趣的SQL Server 层级汇总数据问题
  • 原文地址:https://www.cnblogs.com/pythonwl/p/15043963.html
Copyright © 2011-2022 走看看