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 with
blocks).
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 verbose
of code, if juxtaposed True
, will output the internal structure of the named tuple. In fact, it is a tuple
class 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 FrenchDeck
the __getitem__
method is implemented, it can be manipulated like an OR List
, such as random access, slicing:Tuple
FrenchDeck
>>> 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, FrenchDeck
it is still an iterable object, that is, it can be for
accessed 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 in
the operator can be used FrenchDeck
on:
>>> Card('2', 'spades') in deck
True
If you deck
call the function on the above variable sorted
, Python will ASCII
sort 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 PyVarObject
the ob_size
attributes in it directly, PyVarObject
but 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 x
is 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
, str
etc.) 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 print
function 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 Vector
classes implement __bool__
methods that can be used in contexts where boolean values are required ( if
, while
, and
, or
, not
etc.). 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 bool
returns 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). len
The reason why it is not an ordinary method is to allow the data structure that comes with Python to go through the back door, abs
and the same is true. But thanks to it's special method, we can also use it len
for 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.