zoukankan      html  css  js  c++  java
  • 《Fluent Python》- 01 Python数据模型

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

    一摞Python风格的纸牌

    主要说明两个方法   __getitem__  以及  __len__

    Card = collections.namedtuple('Card', ['rank', 'suit'])
    # namedtuple,tuple的一种,不可变
    # 名为Card,后面的rank,suit是其属性,简单来说就是一个不可变的对象包含['a', 'b']两个属性
    # 可以通过 my_card = Card('rank', 'suit') 的方式简单的构造
    class FrenchDeck:
        ranks = [str(n) for n in range(2, 11)] + list('JQKA')  # 构造 '2 3 4...10 J Q K A'
        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):
            return len(self._cards)
    
        def __getitem__(self, item):  # 为了实现 obj[item] 这个操作
            return self._cards[item]

    首先我们构造出这个FrenchDeck类,主要是包含有两个方法 __getitem__  以及  __len__

    当一个类实现了__getitem__方法时,便可以使用obj[index] 这种类似列表的方式去访问其元素,而 __len__ 方法是为了能让  len() 函数用作

    简单来说,如果你在代码里用了obj[index] 会访问到  __getitem__ 方法,另一个同理

    deck = FrenchDeck()
    print(len(deck))  # 52
    print(deck[0]) # Card(rank='2', suit='spades')
    print(choice(deck))  # choice 从列表中随机访问   Card(rank='6', suit='diamonds')

    符合预期值,其实一开始我们可能会有点不习惯,为什么要用len,而不是用  .length()  或者 .size() 这种方法来获取长度。Python采用这种方式其实也有其好处,假象一下,我们在Java中获取长度,有时可能不知道对方是采用了length() 方法还是size()方法,亦或者其他名称来获取长度,并没有统一的规范。

    ranks列表的构造是采用了列表推导的方式构造了,这个下一节会说明(我刚看时看的也是很懵,不过通过结果倒推其实还是很容易理解的)

    迭代通常是隐式的,譬如说一个集合类型没有实现__contains__方法,那么in运算符就会按顺序做一次迭代搜索。于是in运算符就可以用在我们的FrenchDeck类上:

    print(Card('Q', 'hearts') in deck) # True
    print(Card('Q', 'beasts') in deck) # False

    那么怎么排序呢?我们就按照2,3,4.....K,A 的顺序,再加上花色,黑桃最大,红桃次之,方块再次之,梅花最小。

    suit_values = dict(spades = 3, hearts = 2, diamonds = 1, clubs = 0)
    def spades_high(card):
        rank_value = FrenchDeck.ranks.index(card.rank)
        return rank_value * len(suit_values) + suit_values[card.suit]
    # 这个用来排序的函数有些复杂
    # sorted的里的key可以是函数,也可以是lambda,我们可以把函数抽象成一个lambda
    # 首先要明白这个函数是作用是什么,给定的参数是card换句话说,需要排序的列表里的参数得有card
    # 然后通过这个card做些许操作,得到一个值,通过这个值来比较
    # FrenchDeck.ranks.index(card.rank) 这行其实是获取了FrenchDeck里的ranks,然后通过传入的card来判断,传入的card应该在哪个位置
    # 举个例子,传入了Card(rank='J', suit='diamonds'),那么rank_value得到的答案就是'J'所在的下角标,也就是9
    # 然后返回的是 9 * 4(因为有四种花色) + 这种花色的权值
    # 这个排序的结果就是  (clubs, 2) ,(diamonds, 2) ...  (spades, A)
    
    for card in sorted(deck, key=spades_high):
        print(card)
    
    ###结果
    # Card(rank='2', suit='clubs')
    # Card(rank='2', suit='diamonds')
    # Card(rank='2', suit='hearts')
    # Card(rank='2', suit='spades')
    # ...
    # Card(rank='A', suit='diamonds')
    # Card(rank='A', suit='hearts')
    # Card(rank='A', suit='spades')

    一些特殊方法

    一些特殊方法是为了被Python解释器调用的,而自己并非需要调用这些。就像上面的那个长度,我们是通过len() 来获取,而非直接 .__len__()。

    如果是Python的内置类型,比方说list,str等 CPython会抄近路,直接调用其ob_size属性,因为这个读取是比读函数快的。

    很多时候这些特殊方法的调用是隐式的,比方说for i in x 这个语句,其实是用了iter(x) 方法,背后则是 x.__iter__()。这时你可能就会好奇了,我们的FrenchDeck其实并没有实现iter方法,却可以迭代,这是为什么。其实主要是__getitem__方法的功劳,关于迭代的具体流程之后还会说明。

    还有不要想当然的去添加一些特殊方法,现在没有的,以后说不定会有

    模拟数值类型

    利用特殊方法,可以让自定义对象通过加号‘+’进行运算,简单来说,可以变相实现像C++那种重载运算符

    class Vector:
        def __init__(self, x = 0, y = 0):
            self.x = x
            self.y = y
    
        def __repr__(self):  # 类似于java里的 toString
            return 'Vector(%r, %r)' % (self.x, self.y)
    
        def __abs__(self):  # 获取绝对值,可以通过abs() 来访问
            return hypot(self.x, self.y)  # 通俗来说,就是获取以a, b为边的直角三角形斜边,放这里的意思就是向量长
    
        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)

    关于__repr__方法再多说两句,__repr__几乎等价于Java中的toString()函数(友情提示你一下,Java里你即使实现了toString方法,也不能用String强转)。

    __str__ 函数的用途是在调用 str(obj)的时候访问的,但是print(obj) 是不会调用__str__ 方法的。不论你有没有实现__repr__

    但是反过来说,如果你没有实现__str__ 函数,但是有__repr__函数,那么解释器会帮你调用__repr__方法。

    特殊方法一览

       和运算符无关的特殊方法
    类别 方法名
    字符串/字节序列表示形式 __repr__, __str__, __format__, __bytes__
    数值转换 __abs__, __bool__, __complex__, __int__, __float__, __hash__, __index__
    集合模拟 __len__, __getitem__, __setitem__, __delitem__, __contains__
    迭代枚举 __iter__, __reversed__, __next__
    可调用模拟 __call__
    上下文管理  __enter__, __exit__
    实例创建和销毁 __new__, __init__, __del__
    属性管理 __getattr__, __setattr__, getattribute__, __setattribute__, __delattr__, __dir__
    属性描述符 __get__, __set__, __delete__
    跟类相关的服务  __prepare__, __instancecheck__, __subclasscheck__
      和运算符相关的特殊方法
    类别 方法名和对应的运算符
    一元运算符 __neg__ - , __pos__ +, __abs__  abs()  
    众多比较运算符  __lt__  <,  __le__  <=,  __eq__  =,  __ne__   !=,  __gt__  >, __ge__  >=
    算术运算符 __add__ +, __sub__ - , __mul__ *, __truediv__ /,  __floordiv //,  __mod__ %,  __divmod__  divmod(),  __pow__ **或pow(), __round__  round()
    反向算术运算符 __radd__,  __rsub__,  __rmul__,  __rtruediv__,  __rfloordiv__, __rmod__,  __rdivmod__, __rpow__
    增量赋值算术运算符 __iadd__,  __isub__,  __imul__ , __itruediv__, __ifloordiv__, __imod__, __ipow__
    位运算符 __invert__  ~,  __lshift__ <<,  __rshift__ >>,  __and__ &, __or__ |, __xor__ ^
    反向位运算符 __rlshift__,  __rrshift__, __rand__, __rxor__, __ror__
    增量赋值位运算符 __ilshift__,  __irshift__, __iand__, __ixor__, __ior__

     杂谈(非正式向)

    这个表是真的难操控,姑且就这样吧

    老实说,我最开始看这一节花了很多时间,这第一节确实有些硬核,用到了很多之前见都没见过的方法或者方式。

    有的时候正着不行就反着来,从结果出发倒推代码逻辑,然后去理解里面的用意能帮助我们不少。

  • 相关阅读:
    hdu 1042 N!
    hdu 1521 排列组合 指数型母函数
    soj 3252 Choose 组合数对素数取余
    hrbeu 错排问题
    Java 垃圾回收机制浅析
    Java 简单了解线程 同步线程和死锁(二)
    Java 简单了解线程 生产者与消费者问题(三)
    Java 网络编程 简单接触UDP
    Java 简单接触Applet
    Java 控制台的输入和由Hello World引发的两个小问题
  • 原文地址:https://www.cnblogs.com/Moriarty-cx/p/12334730.html
Copyright © 2011-2022 走看看