ZetCode

Python 魔术方法

最后修改日期:2024 年 3 月 14 日

在本文中,我们将介绍 Python 魔术方法是什么以及如何使用它们。我们将介绍一些常见的魔术方法。

Python 魔术方法 是特殊方法,用于为我们的自定义类添加功能。它们被双下划线包围(例如 __add__())。(魔术方法也称为 dunder 方法。)

Python 中有很多魔术方法。其中大多数用于非常具体的情况。我们将提及一些更流行的方法。

__add__ 方法

__add__ 方法用于实现加法运算。在 Python 中,数字不是原始字面量,而是对象。num + 4 表达式等效于 num.__add__(4)

main.py
#!/usr/bin/python

class MyDict(dict):

    def __add__(self, other):

        self.update(other)
        return MyDict(self)


a = MyDict({'de': 'Germany'})
b = MyDict({'sk': 'Slovakia'})

print(a + b)

在示例中,我们有一个自定义字典,它使用 __add__ 实现加法运算。

class MyDict(dict):

def __add__(self, other):

    self.update(other)
    return MyDict(self)

该自定义字典继承自内置的 dict__add__ 方法使用 update 方法添加两个字典,并返回新创建的字典。

a = MyDict({'de': 'Germany'})
b = MyDict({'sk': 'Slovakia'})

我们创建了两个简单的字典。

print(a + b)

我们添加了这两个字典。

$ ./main.py
{'de': 'Germany', 'sk': 'Slovakia'}

__init__ 和 __str__ 方法

__init__ 方法用于初始化对象。此方法用于实现对象的构造函数。__str__ 提供对象的可读性输出。

main.py
#!/usr/bin/python

class Person:

    def __init__(self, name, occupation):

        self.name = name
        self.occupation = occupation

    def __str__(self):

        return f'{self.name} is a {self.occupation}'


p = Person('John Doe', 'gardener')
print(p)

在示例中,我们有一个 Person 类,它有两个属性:nameoccupation

def __init__(self, name, occupation):

    self.name = name
    self.occupation = occupation

__init__ 方法中,我们将实例变量设置为传递给构造函数的值。

def __str__(self):

    return f'{self.name} is a {self.occupation}'

__str__ 方法提供了对象的简洁输出。

$ ./main.py
John Doe is a gardener

__repr__ 方法

__repr__ 方法由内置函数 repr 调用。当 Python shell 求值返回对象的表达式时,它会被使用。

__str__ 用于提供对象的可读版本,而 __repr__ 提供对象的完整表示。后者的输出也更适合开发者。

如果缺少 __str__ 实现,则 __repr__ 方法将作为回退使用。

def __repr__(self):
    return '<{0}.{1} object at {2}>'.format(
      self.__module__, type(self).__name__, hex(id(self)))

对象 __repr__ 方法的默认实现如下所示。

main.py
#!/usr/bin/python

class Person:

    def __init__(self, name, occupation):
        
        self.name = name
        self.occupation = occupation

    def __str__(self):

        return f'{self.name} is a {self.occupation}'

    def __repr__(self):

        return f'Person{{name: {self.name}, occupation: {self.occupation}}}'


p = Person('John Doe', 'gardener')

print(p)
print(repr(p))

该示例同时实现了 __str____repr__ 方法。

$ ./main.py
John Doe is a gardener
Person{name: John Doe, occupation: gardener}

__format__ 方法

__format__ 方法使我们能够更好地控制对象在 f-string 中的格式。它允许我们根据 f-string 中提供的格式说明符定义自定义格式行为。

main.py
#!/usr/bin/python

from dataclasses import dataclass

@dataclass
class User:
    name: str
    occupation: str

    def __format__(self, spec):
        return f'User(name={self.name}{spec} occupation={self.occupation})'


u1 = User('John Doe', 'gardener')
u2 = User('Roger Roe', 'driver')
u3 = User('Lucia Smith', 'teacher')

print(f'{u1:-}')
print(f'{u2:;}')
print(f'{u3:#}')

我们定义了一个具有自定义 __format__ 方法的数据对象。

def __format__(self, spec):
    return f'{self.name} {spec} {self.occupation}'

该方法将提供的说明符放置在对象的字段之间。

$ python main.py
User(name=John Doe- occupation=gardener)
User(name=Roger Roe; occupation=driver)
User(name=Lucia Smith# occupation=teacher)

__len__ 和 __getitem__ 方法

__len__ 方法返回容器的长度。当我们对对象使用内置的 len 方法时,就会调用此方法。__getitem__ 方法定义了项目访问([])运算符。

main.py
#!/usr/bin/python

import collections
from random import choice


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


class FrenchDeck:

    ranks = [str(i) for i in range(2, 11)] + list('JQKA')
    suits = ["heart", "clubs", "spades", "diamond"]

    def __init__(self):
        self.total = [Card(suit, rank)
                           for suit in self.suits for rank in self.ranks]

    def __len__(self):
        return len(self.total)

    def __getitem__(self, index):
        return self.total[index]


deck = FrenchDeck()

print(deck[0])
print(len(deck))
print(choice(deck))

这些方法用于实现一副法国纸牌。

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

我们使用命名元组来定义 Card 类。namedtuple 是一个用于创建元组类的工厂函数。每张牌都有花色和点数。

def __len__(self):
    return len(self.total)

__len__ 方法返回牌组中的牌数(52)。

def __getitem__(self, index):
    return self.total[index]

__getitem__ 实现索引操作。

print(deck[0])

我们获取牌组的第一张牌。这会调用 __getitem__

print(len(deck))

这会调用 __len__ 方法。

$ ./main.py
Card(suit='heart', rank='2')
52
Card(suit='diamond', rank='A')

__int__ 和 __index__ 方法

__int__ 方法用于实现内置的 int 函数。当对象在切片表达式以及内置的 hexoctbin 函数中使用时,__index__ 方法会实现到 int 的类型转换。

main.py
#!/usr/bin/python

class Char:

    def __init__(self, val):
        self.val = val

    def __int__(self):
        return ord(self.val)

    def __index__(self):
        return ord(self.val)


c1 = Char('a')

print(int(c1))
print(hex(c1))
print(bin(c1))
print(oct(c1))

在示例中,我们创建了一个自定义 Char 类,它实现了 inthexbinoct 函数。

$ ./char_ex.py
97
0x61
0b1100001
0o141

__eq__、__lt__ 和 __gt__ 方法

__eq__ 实现 == 运算符。__lt__ 实现 < 运算符,__gt__ 实现 > 运算符。

main.py
#!/usr/bin/python

import collections

Coin = collections.namedtuple('coin', ['rank'])

# a gold coin equals to two silver and six bronze coins


class Pouch:

    def __init__(self):
        self.bag = []

    def add(self, coin):

        self.bag.append(coin)

    def __eq__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 == val2:
            return True
        else:
            return False

    def __lt__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 < val2:
            return True
        else:
            return False

    def __gt__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 > val2:
            return True
        else:
            return False

    def __str__(self):

        return str(self.bag)

    def __evaluate(self, other):

        val1 = 0
        val2 = 0

        for coin in self.bag:

            if coin.rank == 'g':
                val1 += 6

            if coin.rank == 's':
                val1 += 3

            if coin.rank == 'b':
                val1 += 1

        for coin in other.bag:

            if coin.rank == 'g':
                val2 += 6

            if coin.rank == 's':
                val2 += 3

            if coin.rank == 'b':
                val2 += 1

        return val1, val2


pouch1 = Pouch()

pouch1.add(Coin('g'))
pouch1.add(Coin('g'))
pouch1.add(Coin('s'))

pouch2 = Pouch()

pouch2.add(Coin('g'))
pouch2.add(Coin('s'))
pouch2.add(Coin('s'))
pouch2.add(Coin('b'))
pouch2.add(Coin('b'))
pouch2.add(Coin('b'))

print(pouch1)
print(pouch2)

if pouch1 == pouch2:
    print('Pouches have equal value')

elif pouch1 > pouch2:
    print('Pouch 1 is more valueable than Pouch 2')
else:
    print('Pouch 2 is more valueable than Pouch 1')

我们有一个袋子,里面可以装金币、银币和铜币。一枚金币等于两枚银币和六枚铜币。在示例中,我们使用 Python 魔术方法为袋子对象实现了这三个比较运算符。

def __eq__(self, other):

    val1, val2 = self.__evaluate(other)

    if val1 == val2:
        return True
    else:
        return False

__eq__ 方法中,我们首先计算两个袋子的值。然后我们对它们进行比较并返回布尔结果。

def __evaluate(self, other):

    val1 = 0
    val2 = 0

    for coin in self.bag:

        if coin.rank == 'g':
            val1 += 6

        if coin.rank == 's':
            val1 += 3

        if coin.rank == 'b':
            val1 += 1

    for coin in other.bag:

        if coin.rank == 'g':
            val2 += 6

        if coin.rank == 's':
            val2 += 3

        if coin.rank == 'b':
            val2 += 1

    return val1, val2

__evaluate 方法计算两个袋子的值。它遍历袋子中的硬币,并根据硬币的等级添加一个值。

pouch1 = Pouch()

pouch1.add(Coin('g'))
pouch1.add(Coin('g'))
pouch1.add(Coin('s'))

我们创建了第一个袋子并向其中添加了三枚硬币。

if pouch1 == pouch2:
    print('Pouches have equal value')

elif pouch1 > pouch2:
    print('Pouch 1 is more valueable than Pouch 2')
else:
    print('Pouch 2 is more valueable than Pouch 1')

我们使用比较运算符比较这些袋子。

二维向量示例

在下面的示例中,我们介绍了一些其他的魔术方法,包括 __sub____mul____abs__

main.py
#!/usr/bin/python

import math


class Vec2D:

    def __init__(self, x, y):

        self.x = x
        self.y = y

    def __add__(self, other):
        return Vec2D(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vec2D(self.x - other.x, self.y - other.y)

    def __mul__(self, other):
        return self.x * other.x + self.y * other.y

    def __abs__(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __str__(self):
        return f'({self.x}, {self.y})'

    def __ne__(self, other):
        return not self.__eq__(other)  


u = Vec2D(0, 1)
v = Vec2D(2, 3)
w = Vec2D(-1, 1)

a = u + v
print(a)

print(a == w)

a = u - v
print(a)

a = u * v

print(a)
print(abs(u))
print(u == v)
print(u != v)

在示例中,我们有一个 Vec2D 类。我们可以比较、添加、减去和乘以向量。我们还可以计算向量的长度。

$ ./main.py
(2, 4)
False
(-2, -2)
3
1.0
False 
True

来源

Python 文档

在本文中,我们学习了 Python 魔术方法。

作者

我叫 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Python 教程