Python Learning Road 20 - Data Model

1 Introduction

The data model is actually a description of the Python framework, which standardizes the interface of the language's own component modules, including but not limited to sequences, iterators, functions, classes, and context managers. No matter what framework you write your program in, you will spend a lot of time implementing the methods that will be called by the framework itself, and Python is no exception. When the Python interpreter encounters special syntax, it will use special methods to activate some basic object operations. The names of these special methods start with two underscores and end with two underscores (so special methods are also called dunder methods). These special method names allow objects you write to implement, support, and interact with the following language frameworks:

Iteration, collection classes, property access, operator overloading, invocation of functions and methods, creation and destruction of objects, string representation and formatting, managing context (ie withblocks).

The following are some examples of commonly used special methods.

2. Python Style Solitaire

First, two special methods __getitem__and __len__these two special methods are introduced. The following code creates a Solitaire class:

import collections

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

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):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

namedtuple, that is, a named tuple, similar C/C++to in struct, is defined as follows:

collections.namedtuple(typename, field_names, verbose=False, rename=False)

The first parameter is the tuple name; the second is the attribute name contained in the tuple; the third parameter indicates that the structure of the named tuple is printed out before constructing the named tuple. 3 lines verboseof code, if juxtaposed True, will output the internal structure of the named tuple. In fact, it is a tupleclass inherited from. Since the output is too long, please experiment by yourself; if the element name of the named tuple contains Python keyword, you need to set the fourth parameter as True, these element names with the same name as the keyword will be specially treated.

Creating an object with no methods from a named tuple is simple:

>>> from chapter20 import Card, FrenchDeck
>>> beer_card = Card("7", "diamonds")
>>> beer_card
Card(rank='7', suit='diamonds')

Since FrenchDeckthe __getitem__method is implemented, it can be manipulated like an OR List, such as random access, slicing:TupleFrenchDeck

>>> deck = FrenchDeck()
>>> len(deck)
52
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')
>>> from random import choice
>>> choice(deck)
Card(rank='4', suit='clubs')
>>> choice(deck)
Card(rank='J', suit='clubs')
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), 
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

Since this method is implemented, FrenchDeckit is still an iterable object, that is, it can be foraccessed with a loop (or reverse access reversed):

>>> for card in deck:
>>> ... print(card)

Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
-- snip --
Card(rank='Q', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='A', suit='hearts')

Iteration is usually implicit. For example, if a collection type does not implement a __contains__method, then the in operator will do an iterative search (call __getitem__) in order, so inthe operator can be used FrenchDeckon:

>>> Card('2', 'spades') in deck
True

If you deckcall the function on the above variable sorted, Python will ASCIIsort by yardage, but this is not the correct ordering of playing cards, so here's our custom sorting method:

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]

for card in sorted(deck, key=spades_high):
    print(card)

At this time, the output result is first sorted by points, and then sorted by suit.

3. How to use special methods

To be clear, the existence of special methods is to be called by the Python interpreter. As a programmer, you do not need to call them, that is to say, there is no my_object.__len__()such way of writing, but it should be len(my_object). Speaking __len__of methods, if it is a built-in type in Python, CPython will take a shortcut. The method will actually return PyVarObjectthe ob_sizeattributes in it directly, PyVarObjectbut the C language structure representing the variable-length internal hemorrhoid object in memory.

Many times the invocation of special methods is implicit, such as for i in x:this statement, which is actually used behind it iter(x), and behind this function is a x.__iter__()method, of course, provided that the method xis implemented in (if it is not implemented, the __getitem__method will be called).

Calling the value directly is much faster than calling a method. The frequency of calling special methods directly should be far less than the number of times you need to implement them.

Using special methods via built-in functions (eg len, iter, stretc.) is the best option. Not only do these built-in functions call special methods, they often provide additional benefits, but they are faster for built-in classes.

One more thing worth noting: don't automatically add special methods __foo__like this, because while the name isn't used internally by Python right now, it won't be the case in the future.

3.1 Custom vector Vector

String output using 5 special methods Vector, taking absolute value (modulo if complex), returning boolean value, addition and multiplication operations:

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)

    def __abs__(self):
        return hypot(self.x, self.y)

    # 在Python中,只有0,NULL才是False,其余均为True
    def __bool__(self):
        # 更简单的写法是:
        # return bool(self.x or self.y)
        return bool(abs(self))

    # 实现加法运算符重载
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    # 实现乘法运算符重载,这里是数乘,且还没有实现交换律(需要实现__rmul__方法)
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

Python has a built-in function called repr. This function uses a special method __repr__to get the string representation of an object. Without this special method, when we print a vector object to the console, the resulting string may be <Vector object at 0x10e00070>:

# 代码:
v1 = Vector(2, 4)
v2 = Vector(2, 1)
print(v1 + v2)
print(abs(v1))
print(v1 * 3)

# 结果:
Vector(4, 5)
4.47213595499958
Vector(6, 12)

__repr__The __str__difference and connection with: the former is convenient for us to debug and record logs, while the latter is for end users. The latter is called when the str()function is used, or when the printfunction prints an object, and it returns a string that is end-user friendly. If you only want to implement one of these two special methods, it __repr__is a better choice, because if an object does not have a __str__function and Python needs to call it, the interpreter will use it __repr__instead.

The above Vectorclasses implement __bool__methods that can be used in contexts where boolean values ​​are required ( if, while, and, or, notetc.). By default, instances of classes we define ourselves are always considered True, unless overridden __bool__or __len__methods of this class are overridden. bool(x)Behind the call is the call x.__bool__(); if the method does not exist __bool__, the bool(x)call is attempted x.__len__(), and if the method returns 0, it boolreturns False, otherwise it returns True.

3.2 Why len is not a common method

"Practical is better than pure" (a phrase from the Zen of Python). lenThe reason why it is not an ordinary method is to allow the data structure that comes with Python to go through the back door, absand the same is true. But thanks to it's special method, we can also use it lenfor custom data types. This way of handling finds a balance between maintaining the efficiency of built-in types and ensuring the consistency of the language, and also confirms another sentence in the "Zen of Python": "You can't make special cases so special that exams break the established rules."

4. Summary

By implementing special methods, custom data types can behave like built-in types, allowing us to write more Pythonic code. The following content will revolve around more special methods.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325615348&siteId=291194637