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__

     杂谈(非正式向)

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

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

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

  • 相关阅读:
    jsp转向
    什么是 XDoclet?
    tomcat中的几点配置说明
    mysql5问题
    POJ 3734 Blocks
    POJ 2409 Let it Bead
    HDU 1171 Big Event in HDU
    POJ 3046 Ant Counting
    HDU 2082 找单词
    POJ 1286 Necklace of Beads
  • 原文地址:https://www.cnblogs.com/Moriarty-cx/p/12334730.html
Copyright © 2011-2022 走看看