• 第一章Python 数据模型


    数据模型其实是对Python框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列、迭代器、函数、类和上下文管理器。

           magic and dunder:魔术方法(magic method)是特殊方法的昵称。有些开发者在提到__getitem__这个特殊方法的时候,会用诸如“下划线--下划线--getitem”这种说法。但会引起歧义,因为像__x这种命名在Python里面也有其他含义。一位大牛用了“双下 -- getitem”这种说法。于是特殊方法也称为双下方法(dunder method)

    (一)一摞Python风格的纸牌

    Propaedeutics:

    Python元组的升级版本 -- namedtuple(具名元组)

    因为元组的局限性:不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义,所以在这里引入了 collections.namedtuple 这个工厂函数,来构造一个带字段名的元组。具名元组的实例和普通元组消耗的内存一样多,因为字段名都被存在对应的类里面。这个类跟普通的对象实例比起来也要小一些,因为 Python 不会用 __dict__ 来存放这些实例的属性。

    namedtuple 对象的定义如以下格式:

    collections.namedtuple(typename, field_names, verbose=False, rename=False) 
    返回一个具名元组子类 typename,其中参数的意义如下:
      typename:元组名称
      field_names: 元组中元素的名称
      rename: 如果元素名称中含有 python 的关键字,则必须设置为 rename=True
      verbose: 默认就好
    

     

    import collections
    from random import choice
    
    Card = collections.namedtuple('Card',['rank','suit'])
    
    class FrenchDeck:
        # ranks = [str(n) for n in range(2,11)] + list('JQAK')
        rank = [str(n) for n in range(2,11)] + list('JQAK')
        suits = 'spades diamonds clubs hearts'.split()
    
        def __init__(self):
            self._cards = [Card(rank,suit) for suit in self.suits for rank in self.rank]
    
        def __len__(self):
            return len(self._cards)
        """
        如果在类中定义了__getitem__()方法,那么它的实例对象(假设为P)就可以这样P[key]取值。
        当实例对象做P[key]运算时,就会调用类中的__getitem__()方法。
        """
        def __getitem__(self, position):
            return self._cards[position]
    
    
    
    beer_card = Card('7','diamonds')
    print(beer_card)   # Card(rank='7', suit='diamonds')
    
    deck = FrenchDeck()
    # 使用len()函数查看一叠牌多少张
    print(len(deck))   # 52
    # 抽取特定的一张牌 ,这都是由__getitem__方法提供的
    print(deck[0])     # Card(rank='2', suit='spades')
    print(deck[-1])    # Card(rank='K', suit='hearts')
    # 随机抽取一张牌,使用内置的随机选出一个元素的函数 random.choice
    print(choice(deck))# Card(rank='J', suit='hearts')
    print(choice(deck))# Card(rank='5', suit='hearts')
    # 由于__getitem__方法把[]操作交给了self._cards列表故支持切片操作(slicing)操作
    print(deck[:3])    # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
    print(deck[12::13])
    
    # 另外仅仅实现了 __getitem__函数,这摞牌就变成可迭代了
    for card in deck:
        print(card)
    # 方向迭代操作
    for card in reversed(deck):
        print(card)
    
    # 迭代通常是隐示的,若一个集合类型没有实现__contains__方法,那么in运算符就会按照顺序做一次迭代搜索
    print(Card('Q','hearts')in deck)
    
    # 排序操作
    suit_values = dict(spades = 3,hearts = 2,diamonds = 1,clubs = 0)
    def spades_high(card):
        rank_value = FrenchDeck.rank.index(card.rank)
        return rank_value * len(suit_values) + suit_values[card.suit]
    
    for card in sorted(deck,key= spades_high):
        print(card)
    

    (二)如何使用特殊方法?

      首先特殊方法的存在是为了被Python解释器调用的,你自己并不需要调用它。也就是说没有 obj.__len__() 这种写法,而是使用 len(obj) 这种写法。

        1、若obj是你自己自定义类对象,Python会去调用你写的__len__方法。

        2、若obj是Python内置的类型,比如列表(list)、字符串(str)、字节序列(bytearray)等,那么CPython会抄个近路,__len__实际上会返回PyVarObject里的ob_size属性。PyVarObject是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用方法快的多。

           很多时候,特殊方法的调用是隐式的,比如for I in x:这个语句,背后其实用的是iter(x),而这个函数背后则是x.__iter__()方法。当然前提是这个方法在x中被实现了。

    (三)模拟数值类型

    案例:实现二维向量类

    Propaedeutics:

      abs:是一个内置的函数,输入整数或浮点数,它返回输入值的绝对值;如果输入是复数(complex number),则返回复数的模。

    from math import hypot
    
    class Vector:
        def __init__(self,x=0,y=0):
            self.x = x
            self.y = y
    
        def __repr__(self):
            return 'Vector(%r,%r)' % (self.x,self.y)
    
        # hypot 返回欧几里德范数 sqrt(x*x + y*y)
        def __abs__(self):
            return hypot(self.x,self.y)
    
        def __bool__(self):
            return bool(abs(self))
    
        def __add__(self, other):
            x = self.x + other.x
            y = self.y + other.y
            return Vector(x,y)
    
        def __mul__(self, scalar):
            return Vector(self.x * scalar,self.y * scalar)
    
    v1 = Vector(2,4)
    v2 = Vector(2,1)
    print(v1 + v2)
    

    字符串表示形式:

    (注:%和str.format这两种字符串的格式化手段都会使用)

    在__repr__的实现中,我们用到了%r来获取对象各个属性的标准字符串表示形式——这是个好习惯,它暗示了一个关键:Vector(1,2)和Vector(‘1’,’2’)是不一样的,后者在我们的定义中会报错,因为向量的构造函数直接受数值而非字符串。

    __repr__所返回的字符串应该准确、无歧义,并且尽可能表达出如何用代码创建出这个被打印的对象。

    __repr__和__str__的区别在于,后者是在str()函数中被使用,或是再用print函数打印一个对象的时候才被调用的,并且返回的字符串对终端用户友好。

           若你想实现两个方法中的一个,__repr__是更好选择。如果一个对象没有__str__函数,而python又需要调用它的时候,解释器会用__repr__来代替。

    %r:将变量传递到reor()函数中,结果是将变量转化成适合机器阅读的格式

    %s:将变量传递到str()函数中,结果是将变量转化成适合人阅读的格式

    算术运算:

    通过__add__和__mul__,上例为向量类带来了+和*这两个算术运算。值得注意的是,这两个方法的返回值都是新创建的向量对象,被操作的两个向量(self或other)还是原封不动,代码里只读取了它们的值而已。中缀运算符的基本原则就是不改变操作的对象,而是产生一个新的值。

    自定义bool值:

    为了判定一个值x为真还是为假,Python会调用bool(x),这个函数只能返回True/False。默认情况下,我们始终认为一个对象是True,除非这个类对__bool__或者__len__函数有自己的实现。bool(x)的背后调用的是x.__bool__()的结果;如果不存在__bool__方法,那么bool(x)会尝试调用x.__len__()方法。若返回0则为False,否则返回True。

    特殊方法一览表:

    类的构造、删除:
    object.__new__(self, ...)
    object.__init__(self, ...)
    object.__del__(self)
    
    二元操作符:
    +    object.__add__(self, other)
    -    object.__sub__(self, other)
    *    object.__mul__(self, other)
    //    object.__floordiv__(self, other)
    /    object.__div__(self, other)
    %    object.__mod__(self, other)
    **    object.__pow__(self, other[, modulo])
    <<    object.__lshift__(self, other)
    >>    object.__rshift__(self, other)
    &    object.__and__(self, other)
    ^    object.__xor__(self, other)
    |    object.__or__(self, other)
    
    扩展二元操作符:
    +=    object.__iadd__(self, other)
    -=    object.__isub__(self, other)
    *=    object.__imul__(self, other)
    /=    object.__idiv__(self, other)
    //=    object.__ifloordiv__(self, other)
    %=    object.__imod__(self, other)
    **=    object.__ipow__(self, other[, modulo])
    <<=    object.__ilshift__(self, other)
    >>=    object.__irshift__(self, other)
    &=    object.__iand__(self, other)
    ^=    object.__ixor__(self, other)
    |=    object.__ior__(self, other)
    
    一元操作符:
    -    object.__neg__(self)
    +    object.__pos__(self)
    abs()    object.__abs__(self)
    ~    object.__invert__(self)
    complex()    object.__complex__(self)
    int()    object.__int__(self)
    long()    object.__long__(self)
    float()    object.__float__(self)
    oct()    object.__oct__(self)
    hex()    object.__hex__(self)
    round()    object.__round__(self, n)
    floor()    object__floor__(self)
    ceil()    object.__ceil__(self)
    trunc()    object.__trunc__(self)
    
    比较函数:
    <    object.__lt__(self, other)
    <=    object.__le__(self, other)
    ==    object.__eq__(self, other)
    !=    object.__ne__(self, other)
    >=    object.__ge__(self, other)
    >    object.__gt__(self, other)
    
    类的表示、输出:
    str()    object.__str__(self) 
    repr()    object.__repr__(self)
    len()    object.__len__(self)
    hash()    object.__hash__(self) 
    bool()    object.__nonzero__(self) 
    dir()    object.__dir__(self)
    sys.getsizeof()    object.__sizeof__(self)
    
    类容器:
    len()    object.__len__(self)
    self[key]    object.__getitem__(self, key)
    self[key] = value    object.__setitem__(self, key, value)
    del[key] object.__delitem__(self, key)
    iter()    object.__iter__(self)
    reversed()    object.__reversed__(self)
    in操作    object.__contains__(self, item)
    字典key不存在时    object.__missing__(self, key)

    相关查阅:

    https://zhuanlan.zhihu.com/p/24567545

  • 相关阅读:
    Android内存优化杂谈
    ANDROID内存优化(大汇总——全)
    ANDROID内存优化(大汇总——中)
    ANDROID内存优化(大汇总——上)
    Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)
    Java反射学习总结五(Annotation(注解)-基础篇)
    Java反射学习总结四(动态代理使用实例和内部原理解析)
    Java反射学习总结三(静态代理)
    Java反射学习总结二(用反射调用对象的私有属性和方法)
    Java反射学习总结一(基础篇)
  • 原文地址:https://www.cnblogs.com/IamJiangXiaoKun/p/10208000.html
Copyright © 2020-2023  润新知