zoukankan      html  css  js  c++  java
  • Python数据模型及Pythonic编程

    Python作为一种多范式语言,它的很多语言特性都能从其他语言上找到参照,但是Python依然形成了一套自己的“Python 风格”(Pythonic)。这种Pythonic风格完全体现在 Python 的数据模型上,而数据模型中的元接口(指那些名字以两个下划线开头,以两个下划线结尾的特殊方法,例如 __getitem__),就是编写地道的Python代码的秘密所在。这种基于元接口实现的设计模式,也叫鸭子类型(duck typing)。

    鸭子类型指的是对象的类型无关紧要,只要实现了特定的接口即可。忽略对象的真正类型,转而关注对象有没有实现所需的方法、签名和语义。Python的数据模型都支持鸭子类型,鸭子类型也是地道Python编程鼓励的风格,所以如果觉得自己想创建新的抽象基类,先试着通过常规的鸭子类型来解决问题。

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

    得益于 Python 数据模型,自定义类的行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是元接口。Python给类设计了大量的元接口,具体请参看Python 语言参考手册中的“Data Model”章节。下面是一些类的元接口的展示。

    """
        >>> v1 = Vector2d(3, 4)
    
        通过元接口__iter__支持拆包
        >>> x, y = v1
        >>> x, y
        (3.0, 4.0)
    
        通过元接口__repr__支持字面量表示和repr函数
        >>> v1
        Vector2d(3.0, 4.0)
        >>> v1_clone = eval(repr(v1))
        >>> v1 == v1_clone
        True
    
        通过元接口__str__支持print函数
        >>> print(v1)
        (3.0, 4.0)
    
        通过元接口__bytes__支持bytes函数
        >>> octets = bytes(v1)
        >>> octets
        b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
    
        通过元接口__abs__支持abs函数
        >>> abs(v1)
        5.0
    
        通过元接口__bool__支持bool函数
        >>> bool(v1), bool(Vector2d(0, 0))
        (True, False)
    
        通过property支持可读属性
        >>> v1.x, v1.y
        (3.0, 4.0)
        >>> v1.x = 123
        Traceback (most recent call last):
          ...
        AttributeError: can't set attribute
    
        通过__hash__支持对象可散列,支持dict、set等函数
        >>> hash(v1)
        7
        >>> set(v1)
        {3.0, 4.0}
        >>> {v1: 'point1'}
        {Vector2d(3.0, 4.0): 'point1'}
    
    """
    
    from array import array
    import math
    
    class Vector2d:
        typecode = 'd'
    
        def __init__(self, x, y):
            self.__x = float(x) 
            self.__y = float(y)
    
        @property
        def x(self):
            return self.__x
    
        @property
        def y(self):
            return self.__y
    
        def __iter__(self):
            return (i for i in (self.x, self.y))
    
        def __repr__(self):
            class_name = type(self).__name__
            return '{}({!r}, {!r})'.format(class_name, *self)
    
        def __str__(self):
            return str(tuple(self))
    
        def __bytes__(self):
            return (bytes([ord(self.typecode)]) +
                    bytes(array(self.typecode, self)))
    
        def __eq__(self, other):
            return tuple(self) == tuple(other)
    
        def __hash__(self):
            return hash(self.x) ^ hash(self.y)
    
        def __abs__(self):
            return math.hypot(self.x, self.y)
    
        def __bool__(self):
            return bool(abs(self))

    函数

     Python中一切皆对象,函数也不例外,而且Python中的函数还是一等对象。函数可以理解为一种可调用对象语法糖。

    可调用对象的元接口是__call__。如果一个类定义了 __call__ 方法,那么它的实例可以作为函数调用。示例如下。

    """
    >>> pickcard = Cards(range(52))
    >>> pickcard()
    51
    >>> pickcard()
    50
    >>> callable(pickcard)
    True
    """
    class Cards:
        def __init__(self, items):
            self._items = list(items)
    
        def __call__(self):
            return self._items.pop()

    序列

    Python 的序列数据模型的元接口很多,但是对象只需要实现 __len__ 和 __getitem__ 两个方法,就能用在绝大部分期待序列的地方,如迭代,[]运算符、切片、for i in 等操作。示例如下:

    """
    >>> poker = Poker()
    
    支持len运算
    >>> len(poker)
    52
    
    支持[]运算
    >>> poker[0]
    Card(rank='2', suit='spades')
    >>> poker[-1]
    Card(rank='A', suit='hearts')
    
    支持切片运算
    >>> poker[12::13]
    [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
    
    支持 for i in 运算
    >>> for card in poker: print(card)  # doctest: +ELLIPSIS
    ...
    Card(rank='2', suit='spades')
    Card(rank='3', suit='spades')
    Card(rank='4', suit='spades')
    ...
    
    支持 in 运算
    >>> Card('7', 'hearts') in poker
    True
    
    """
    
    import collections
    
    Card = collections.namedtuple('Card', ['rank', 'suit'])
    class Poker:
        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):
            return len(self._cards)
        def __getitem__(self, position):
            return self._cards[position] 

    从测试用例上可以看出它具有序列所有特性,即便它是 object 的子类也无妨。因为它的行为像序列,那我们就可以说它是序列。

    迭代

     Python中,可迭代对象的元接口是__iter__。迭代器可以从可迭代的对象中获取,__iter__和__next__是它的2个主要的元接口。__iter__ 方法使对象支持迭代,__next__ 方法返回序列中的下一个元素。如果没有元素了,那么抛出 StopIteration 异常。

    迭代器可以迭代,但是可迭代的对象不是迭代器,也一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__ 方法,但不能实现 __next__ 方法。

    只要实现__iter__接口的对象,就是迭代鸭子类型,自然就支持所有的迭代运算。示例如下:

    """
    >>> s = Sentence('hello world')
    >>> s
    Sentence('hello world')
    
    支持迭代list运算
    >>> list(s)
    ['hello', 'world']
    
    获取迭代器
    >>> it = iter(s)
    
    支持迭代器next运算
    >>> next(it)
    'hello'
    >>> next(it)
    'world'
    >>> next(it)
    Traceback (most recent call last):
      ...
    StopIteration
    
    支持迭代for运算
    >>> for w in s: print(w)
    hello
    world
    """
    
    import re
    import reprlib
    
    RE_WORD = re.compile('w+')
    
    class Sentence:
    
        def __init__(self, text):
            self.text = text
    
        def __repr__(self):
            return 'Sentence(%s)' % reprlib.repr(self.text)
    
        def __iter__(self):
            word_iter = RE_WORD.finditer(self.text)  
            return SentenceIter(word_iter)  
    
    
    class SentenceIter():
    
        def __init__(self, word_iter):
            self.word_iter = word_iter  
    
        def __next__(self):
            match = next(self.word_iter)  
            return match.group()  
    
        def __iter__(self):
            return self

    上面这个例子中,可迭代对象Sentence通过定义迭代器SentenceIter的方式实现。更Pythonic的做法是通过生成器yield来实现。下面是一个示例,能通过上面的所有测试用例,但代码更加精简。

    RE_WORD = re.compile('w+')
    
    class Sentence:
    
        def __init__(self, text):
            self.text = text 
    
        def __repr__(self):
            return 'Sentence(%s)' % reprlib.repr(self.text)
    
        def __iter__(self):
            for match in RE_WORD.finditer(self.text):
                yield match.group()  

    上下文管理器

     Python的with关键字是上下文管理器语法糖,上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。with 语句开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法,以此扮演 finally 子句的角色。可以看出,上下文管理器简化了 try/finally 模式。下面是一个示例。

    """
    ReversePrint对象的上下文管理,进入with块后,标准输出反序打印,
    退出with块后,标准输出恢复正常状态。
    >>> with ReversePrint() as what:
    ...      print('Hello world!')
    !dlrow olleH
    >>> print('Hello world!') 
    Hello world!
    
    """
    
    class ReversePrint:
    
        def __enter__(self):
            import sys
            self.original_write = sys.stdout.write
            sys.stdout.write = self.reverse_write
            return 'JABBERWOCKY'
    
        def reverse_write(self, text):
            self.original_write(text[::-1])
    
        def __exit__(self, exc_type, exc_value, traceback):
            import sys
            sys.stdout.write = self.original_write
            if exc_type is ZeroDivisionError:
                print('Please DO NOT divide by zero!')
                return True
  • 相关阅读:
    linux下的shell 快捷键
    Python3.x和Python2.x的区别
    [Python2.x] 利用commands模块执行Linux shell命令
    redis缓存策略
    高并发解决方案
    java8 lambda表达式应用
    java读写分离的实现
    数据库读写分离
    大数据量处理方案:分布式数据库
    google的CacheBuilder缓存
  • 原文地址:https://www.cnblogs.com/wahaha02/p/10254049.html
Copyright © 2011-2022 走看看