Python的对象和类

Published on 2017 - 01 - 24

使用class定义类

如果想要在 Python 中创建属于自己的对象,首先你必须用关键词 class 来定义一个类。先来看一个简单的例子。

假设你想要定义一些对象用于记录联系人,每个对象对应一个人。首先需要定义 Person 类作为生产对象的模具。在接下来的几个例子中,我们会不停更新这个类的内容,从最简单的开始,直到它成为一个可以实际使用的类。

首先创建的是最简单的类,即一个没有任何内容的空类:

>>> class Person():
...         pass

同函数一样,用 pass 表示这个类是一个空类。上面这种定义类的方法已经是最简形式,无法再省略。你可以通过类名来创建对象,同调用函数一样:

>>> someone = Person()

在这个例子中,Person() 创建了一个 Person 类的对象,并给它赋值 someone 这个名字。但是,由于我们的 Person 类是空的,所以由它创建的对象 someone 实际上什么也做不了。实际编程中,你永远也不会创建这样一个没用的类,我在这里只是为了从零开始引出后面每一步的内容。

我们来试着重新定义一下 Person 类。这一次,将 Python 中特殊的对象初始化方法 __init__ 放入其中:

>>> class Person():
...         def __init__(self):
...             pass

我承认 __init__() 和 self 看起来很奇怪,但这就是实际的 Python 类的定义形式。__init__() 是 Python 中一个特殊的函数名,用于根据类的定义创建实例对象。self 参数指向了这个正在被创建的对象本身。

当你在类声明里定义 __init__() 方法时,第一个参数必须为 self。尽管 self 并不是一个 Python 保留字,但它很常用。没有人(包括你自己)在阅读你的代码时需要猜测使用 self 的意图。

尽管我们添加了初始化方法,但用这个 Person 类创建的对象仍然什么也做不了。即将进行的第三次尝试要更吸引人了,你将学习如何创建一个简单可用的 Python 对象。这一次,会在初始化方法中添加 name 参数:

>>> class Person():
...         def __init__(self, name):
...             self.name = name
...
>>>

现在,用 Person 类创建一个对象,为 name 特性传递一个字符串参数:

>>> hunter = Person('Elmer Fudd')

上面这短短的一行代码实际做了以下工作:

  • 查看 Person 类的定义;
  • 在内存中实例化(创建)一个新的对象;
  • 调用对象的__init__方法,将这个新创建的对象作为 self 传入,并将另一个参数('Elmer-Fudd')作为 name 传入;
  • 将 name 的值存入对象;
  • 返回这个新的对象;
  • 将名字 hunter 与这个对象关联。

这个新对象与任何其他的 Python 对象一样。你可以把它当作列表、元组、字典或集合中的元素,也可以把它当作参数传递给函数,或者把它做为函数的返回结果。

我们刚刚传入的 name 参数此时又在哪儿呢?它作为对象的特性存储在了对象里。可以直接对它进行读写操作:

>>> print('The mighty hunter: ', hunter.name)
The mighty hunter: Elmer Fudd

记住,在 Person 类定义的内部,你可以直接通过 self.name 访问 name 特性。而当创建了一个实际的对象后,例如这里的 hunter,需要通过 hunter.name 来访问它。

在类的定义中,__init__ 并不是必需的。只有当需要区分由该类创建的不同对象时,才需要指定 __init__ 方法。

继承

在你编写代码解决实际问题时,经常能找到一些已有的类,它们能够实现你所需的大部分功能,但不是全部。这时该怎么办?当然,你可以对这个已有的类进行修改,但这么做很容易让代码变得更加复杂,一不留神就可能会破坏原来可以正常工作的功能。

当然,也可以另起炉灶重新编写一个类:复制粘贴原来的代码再融入自己的新代码。但这意味着你需要维护更多的代码。同时,新类和旧类中实现同样功能的代码被分隔在了不同的地方(日后修改时需要改动多处)。

更好的解决方法是利用类的继承:从已有类中衍生出新的类,添加或修改部分功能。这是代码复用的一个绝佳的例子。使用继承得到的新类会自动获得旧类中的所有方法,而不需要进行任何复制。

你只需要在新类里面定义自己额外需要的方法,或者按照需求对继承的方法进行修改即可。修改得到的新方法会覆盖原有的方法。我们习惯将原始的类称为父类、超类或基类,将新的类称作孩子类、子类或衍生类。这些术语在面向对象的编程中不加以区分。

现在,我们来试试继承。首先,定义一个空类 Car。然后,定义一个 Car 的子类 Yugo。定义子类使用的也是 class 关键词,不过需要把父类的名字放在子类名字后面的括号里(class Yugo(Car)):

>>> class Car():
...         pass
...
>>> class Yugo(Car):
...         pass
...

接着,为每个类创建一个实例对象:

>>> give_me_a_car = Car()
>>> give_me_a_yugo = Yugo()

子类是父类的一种特殊情况,它属于父类。在面向对象的术语里,我们经常称 Yugo 是一个(is-a)Car。对象 give_me_a_yugo 是 Yugo 类的一个实例,但它同时继承了 Car 能做到的所有事情。当然,上面的例子中 Car 和 Yugo 就像潜艇上的甲板水手一样起不到任何实际作用。我们来更新一下类的定义,让它们发挥点儿作用:

>>> class Car():
...         def exclaim(self):
...             print("I'm a Car!")
...
>>> class Yugo(Car):
...         pass
...

最后,为每一个类各创建一个对象,并调用刚刚声明的 exclaim 方法:

>>> give_me_a_car = Car()
>>> give_me_a_yugo = Yugo()
>>> give_me_a_car.exclaim()
I'm a Car!
>>> give_me_a_yugo.exclaim()
I'm a Car!

我们不需要进行任何特殊的操作,Yugo 就自动从 Car 那里继承了 exclaim() 方法。但事实上,我们并不希望 Yugo 在 exlaim() 方法里宣称它是一个 Car,这可能会造成身份危机(无法区分 Car 和 Yugo)。让我们来看看怎么解决这个问题。

覆盖方法

就像上面的例子展示的一样,新创建的子类会自动继承父类的所有信息。接下来将看到子类如何替代——更习惯说覆盖(override)——父类的方法。Yugo 和 Car 一定存在着某些区别,不然的话,创建它又有什么意义?试着改写一下 Yugo 中 exclaim() 方法的功能:

>>> class Car():
...         def exclaim(self):
...             print("I'm a Car!")
...
>>> class Yugo(Car):
...         def exclaim(self):
...             print("I'm a Yugo! Much like a Car, but more Yugo-ish.")
...

现在,为每个类创建一个对象:

>>> give_me_a_car = Car()
>>> give_me_a_yugo = Yugo()

看看它们各自会宣称什么?

>>> give_me_a_car.exclaim()
I'm a Car!
>>> give_me_a_yugo.exclaim()
I'm a Yugo! Much like a Car, but more Yugo-ish.

在上面的例子中,我们覆盖了父类的 exclaim() 方法。在子类中,可以覆盖任何父类的方法,包括 __init__()。下面的例子使用了之前创建过的 Person 类。我们来创建两个子类,分别代表医生(MDPerson)和律师(JDPerson):

>>> class Person():
...         def __init__(self, name):
...             self.name = name
...
>>> class MDPerson(Person):
...         def __init__(self, name):
...             self.name = "Doctor " + name
...
>>> class JDPerson(Person):
...         def __init__(self, name):
...             self.name = name + ", Esquire"
...

在上面的例子中,子类的初始化方法 __init__() 接收的参数和父类 Person 一样,但存储到对象内部 name 特性的值却不尽相同:

>>> person = Person('Fudd')
>>> doctor = MDPerson('Fudd')
>>> lawyer = JDPerson('Fudd')
>>> print(person.name)
Fudd
>>> print(doctor.name)
Doctor Fudd
>>> print(lawyer.name)
Fudd, Esquire

添加新方法

子类还可以添加父类中没有的方法。回到 Car 类和 Yugo 类,我们给 Yugo 类添加一个新的方法 need_a_push():

>>> class Car():
...         def exclaim(self):
...             print("I'm a Car!")
...
>>> class Yugo(Car):
...         def exclaim(self):
...             print("I'm a Yugo! Much like a Car, but more Yugo-ish.")
...         def need_a_push(self):
...             print("A little help here?")
...

接着,创建一个 Car 和一个 Yugo 对象:

>>> give_me_a_car = Car()
>>> give_me_a_yugo = Yugo()

Yugo 类的对象可以响应 need_a_push() 方法:

>>> give_me_a_yugo.need_a_push()
A little help here?

但比它广义的 Car 无法响应该方法:

>>> give_me_a_car.need_a_push()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute 'need_a_push'

至此,Yugo 终于可以做一些 Car 做不到的事情了。它的与众不同的特征开始体现了出来。

使用super从父类得到帮助

我们已经知道如何在子类中覆盖父类的方法,但如果想要调用父类的方法怎么办?“哈哈!终于等到你问这个了。”super() 站出来说道。下面的例子将定义一个新的类 EmailPerson,用于表示有电子邮箱的 Person。首先,来定义熟悉的 Person 类:

>>> class Person():
...         def __init__(self, name):
...             self.name = name
...

下面是子类的定义。注意,子类的初始化方法 __init__()中添加了一个额外的 email 参数:

>>> class EmailPerson(Person):
...         def __init__(self, name, email):
...             super().__init__(name)
...             self.email = email

在子类中定义__init__() 方法时,父类的 __init__() 方法会被覆盖。因此,在子类中,父类的初始化方法并不会被自动调用,我们必须显式调用它。以上代码实际上做了这样几件事情。

  • 通过 super() 方法获取了父类 Person 的定义。
  • 子类的 __init__() 调用了 Person.__init__() 方法。它会自动将 self 参数传递给父类。因此,你只需传入其余参数即可。在上面的例子中,Person() 能接受的其余参数指的是 name。
  • self.email = email 这行新的代码才真正起到了将 EmailPerson 与 Person 区分开的作用。

接下来,创建一个 EmailPerson 类的对象:

>>> bob = EmailPerson('Bob Frapples', 'bob@frapples.com')

我们既可以访问 name 特性,也可以访问 email 特性:

>>> bob.name
'Bob Frapples'
>>> bob.email
'bob@frapples.com'

为什么不像下面这样定义 EmailPerson 类呢?

>>> class EmailPerson(Person):
...         def __init__(self, name, email):
...             self.name = name
...             self.email = email

确实可以这么做,但这有悖我们使用继承的初衷。我们应该使用 super() 来让 Person 完成它应该做的事情,就像任何一个单纯的 Person 对象一样。除此之外,不这么写还有另一个好处:如果 Person 类的定义在未来发生改变,使用 super() 可以保证这些改变会自动反映到 EmailPerson 类上,而不需要手动修改。

子类可以按照自己的方式处理问题,但如果仍需要借助父类的帮助,使用 super() 是最佳的选择(就像现实生活中孩子与父母的关系一样)。

self的自辩

Python 中经常被争议的一点就是必须把 self 设置为实例方法(前面例子中你见到的所有方法都是实例方法)的第一个参数。Python 使用 self 参数来找到正确的对象所包含的特性和方法。通过下面的例子,我会告诉你调用对象方法背后 Python 实际做的工作。

还记得前面例子中的 Car 类吗?再次调用 exclaim() 方法:

>>> car = Car()
>>> car.exclaim()
I'm a Car!

Python 在背后做了以下两件事情:

  • 查找 car 对象所属的类(Car);
  • 把 car 对象作为 self 参数传给 Car 类所包含的 exclaim() 方法。

了解调用机制后,为了好玩,我们甚至可以像下面这样进行调用,这与普通的调用语法(car.exclaim())效果完全一致:

>>> Car.exclaim(car)
I'm a Car!

当然,我们没有理由使用这种臃肿的语法。

使用属性对特性进行访问和设置

有一些面向对象的语言支持私有特性。这些特性无法从对象外部直接访问,我们需要编写 getter 和 setter 方法对这些私有特性进行读写操作。

Python 不需要 getter 和 setter 方法,因为 Python 里所有特性都是公开的,使用时全凭自觉。如果你不放心直接访问对象的特性,可以为对象编写 setter 和 getter 方法。但更具 Python 风格的解决方案是使用属性(property)。

下面的例子中,首先定义一个 Duck 类,它仅包含一个 hidden_name 特性。我们不希望别人能够直接访问这个特性,因此需要定义两个方法:getter 方法(get_name())和 setter 方法(set_name())。我们在每个方法中都添加一个 print() 函数,这样就能方便地知道它们何时被调用。最后,把这些方法设置为 name 属性:

>>> class Duck():
...     def __init__(self, input_name):
...         self.hidden_name = input_name
...     def get_name(self):
...         print('inside the getter')
...         return self.hidden_name
...     def set_name(self, input_name):
...         print('inside the setter')
...         self.hidden_name = input_name
...     name = property(get_name, set_name)

这两个新方法在最后一行之前都与普通的 getter 和 setter 方法没有任何区别,而最后一行则把这两个方法定义为了 name 属性。property() 的第一个参数是 getter 方法,第二个参数是 setter 方法。现在,当你尝试访问 Duck 类对象的 name 特性时,get_name() 会被自动调用:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'

当然,也可以显式调用 get_name() 方法,它就像普通的 getter 方法一样:

>>> fowl.get_name()
inside the getter
'Howard'

当对 name 特性执行赋值操作时,set_name() 方法会被调用:

>>> fowl.name = 'Daffy'
inside the setter
>>> fowl.name
inside the getter
'Daffy'

也可以显式调用 set_name() 方法:

>>> fowl.set_name('Daffy')
inside the setter
>>> fowl.name
inside the getter
'Daffy'

另一种定义属性的方式是使用修饰符(decorator)。下一个例子会定义两个不同的方法,它们都叫 name(),但包含不同的修饰符:

  • @property,用于指示 getter 方法;
  • @name.setter,用于指示 setter 方法。 实际代码如下所示:
>>> class Duck():
...     def __init__(self, input_name):
...         self.hidden_name = input_name
...     @property
...     def name(self):
...         print('inside the getter')
...         return self.hidden_name
...     @name.setter
...     def name(self, input_name):
...         print('inside the setter')
...         self.hidden_name = input_name

你仍然可以像之前访问特性一样访问 name,但这里没有了显式的 get_name() 和 set_name() 方法:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'

在前面几个例子中,我们都使用 name 属性指向类中存储的某一特性(在我们的例子中是 hidden_name)。除此之外,属性还可以指向一个计算结果值。我们来定义一个 Circle 类,它包含 radius 特性以及一个计算属性 diameter:

>>> class Circle():
...     def __init__(self, radius):
...         self.radius = radius
...     @property
...     def diameter(self):
...         return 2 * self.radius
...

创建一个 Circle 对象,并给 radius 赋予一个初值:

>>> c = Circle(5)
>>> c.radius
5

可以像访问特性(例如 radius)一样访问属性 diameter:

>>> c.diameter
10

真正有趣的还在后面。我们可以随时改变 radius 特性的值,计算属性 diameter 会自动根据新的值更新自己:

>>> c.radius = 7
>>> c.diameter
14

如果你没有指定某一特性的 setter 属性(@diameter.setter),那么将无法从类的外部对它的值进行设置。这对于那些只读的特性非常有用:

>>> c.diameter = 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

与直接访问特性相比,使用 property 还有一个巨大的优势:如果你改变了某个特性的定义,只需要在类定义里修改相关代码即可,不需要在每一处调用修改。

使用名称重整保护私有特性

前面的 Duck 例子中,为了隐藏内部特性,我们曾将其命名为 hidden_name。其实,Python 对那些需要刻意隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)。

我们来把 hidden_name 改名为 __name,如下所示:

>>> class Duck():
...     def __init__(self, input_name):
...         self.__name = input_name
...     @property
...     def name(self):
...         print('inside the getter')
...         return self.__name
...     @name.setter
...     def name(self, input_name):
...         print('inside the setter')
...         self.__name = input_name
...

看看代码是否还能正常工作:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'

看起来不错!现在,你无法在外部访问 __name 特性了:

>>> fowl.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute '__name'

这种命名规范本质上并没有把特性变成私有,但 Python 确实将它的名字重整了,让外部的代码无法使用。如果你实在好奇名称重整是怎么实现的,我可以偷偷地告诉你其中的奥秘,但不要告诉别人哦:

>>> fowl._Duck__name
'Donald'

发现了吗?我们并没有得到 inside the getter,成功绕过了 getter 方法。尽管如我们所见,这种保护特性的方式并不完美,但它确实能在一定程度上避免我们无意或有意地对特性进行直接访问。

方法的类型

有些数据(特性)和函数(方法)是类本身的一部分,还有一些是由类创建的实例的一部分。

在类的定义中,以 self 作为第一个参数的方法都是实例方法(instance method)。它们在创建自定义类时最常用。实例方法的首个参数是 self,当它被调用时,Python 会把调用该方法的对象作为 self 参数传入。

与之相对,类方法(class method)会作用于整个类,对类作出的任何改变会对它的所有实例对象产生影响。在类定义内部,用前缀修饰符 @classmethod 指定的方法都是类方法。与实例方法类似,类方法的第一个参数是类本身。在 Python 中,这个参数常被写作 cls,因为全称 class 是保留字,在这里我们无法使用。下面的例子中,我们为类 A 定义一个类方法来记录一共有多少个类 A 的对象被创建:

>>> class A():
...     count = 0
...     def __init__(self):
...         A.count += 1
...     def exclaim(self):
...         print("I'm an A!")
...     @classmethod
...     def kids(cls):
...         print("A has", cls.count, "little objects.")
...
>>>
>>> easy_a = A()
>>> breezy_a = A()
>>> wheezy_a = A()
>>> A.kids()
A has 3 little objects.

注意,上面的代码中,我们使用的是 A.count(类特性),而不是 self.count(可能是对象的特性)。在 kids() 方法中,我们使用的是 cls.count,它与 A.count 的作用一样。

类定义中的方法还存在着第三种类型,它既不会影响类也不会影响类的对象。它们出现在类的定义中仅仅是为了方便,否则它们只能孤零零地出现在代码的其他地方,这会影响代 码的逻辑性。这种类型的方法被称作静态方法(static method)用 @staticmethod 修饰,它既不需要 self 参数也不需要 class 参数。下面例子中的静态方法是一则 CoyoteWeapon 的广告:

>>> class CoyoteWeapon():
...     @staticmethod
...     def commercial():
...         print('This CoyoteWeapon has been brought to you by Acme')
...
>>>
>>> CoyoteWeapon.commercial()
This CoyoteWeapon has been brought to you by Acme

注意,在这个例子中,我们甚至都不用创建任何 CoyoteWeapon 类的对象就可以调用这个方法,句法优雅不失风格!

鸭子类型

Python 对实现多态(polymorphism)要求得十分宽松,这意味着我们可以对不同对象调用同名的操作,甚至不用管这些对象的类型是什么。

我们来为三个 Quote 类设定同样的初始化方法 __init__(),然后再添加两个新函数:

  • who() 返回保存的 person 字符串的值;
  • says() 返回保存的 words 字符串的内容,并添上指定的标点符号。

它们的具体实现如下所示:

>>> class Quote():
...     def __init__(self, person, words):
...         self.person = person
...         self.words = words
...     def who(self):
...         return self.person
...     def says(self):
...         return self.words + '.'
...
>>> class QuestionQuote(Quote):
...     def says(self):
...         return self.words + '?'
...
>>> class ExclamationQuote(Quote):
...     def says(self):
...         return self.words + '!'
...
>>>

我们不需要改变 QuestionQuote 或者 ExclamationQuote 的初始化方式,因此没有覆盖它们的 __init__() 方法。Python 会自动调用父类 Quote 的初始化函数 __init__() 来存储实例变量 person 和 words,这就是我们可以在子类 QuestionQuote 和 ExclamationQuote 的对象里访问 self.words 的原因。

接下来创建一些对象:

>>> hunter = Quote('Elmer Fudd', "I'm hunting wabbits")
>>> print(hunter.who(), 'says:', hunter.says())
Elmer Fudd says: I'm hunting wabbits.

>>> hunted1 = QuestionQuote('Bugs Bunny', "What's up, doc")
>>> print(hunted1.who(), 'says:', hunted1.says())
Bugs Bunny says: What's up, doc?

>>> hunted2 = ExclamationQuote('Daffy Duck', "It's rabbit season")
>>> print(hunted2.who(), 'says:', hunted2.says())
Daffy Duck says: It's rabbit season!

三个不同版本的 says() 为上面三种类提供了不同的响应方式,这是面向对象的语言中多态的传统形式。Python 在这方面走得更远一些,无论对象的种类是什么,只要包含 who() 和 says(),你便可以调用它。我们再来定义一个 BabblingBrook 类,它与我们之前的猎人猎物(Quote 类的后代)什么的没有任何关系:

>>> class BabblingBrook():
...     def who(self):
...         return 'Brook'
...     def says(self):
...         return 'Babble'
...
>>> brook = BabblingBrook()

现在,对不同对象执行 who() 和 says() 方法,其中有一个(brook)与其他类型的对象毫无关联:

>>> def who_says(obj):
...         print(obj.who(), 'says', obj.says())
...
>>> who_says(hunter)
Elmer Fudd says I'm hunting wabbits.
>>> who_says(hunted1)
Bugs Bunny says What's up, doc?
>>> who_says(hunted2)
Daffy Duck says It's rabbit season!
>>> who_says(brook)
Brook says Babble

这种方式有时被称作鸭子类型(duck typing),这个命名源自一句名言:

如果它像鸭子一样走路,像鸭子一样叫,那么它就是一只鸭子。

特殊方法

到目前为止,你已经能创建并使用基本对象了。现在再往深钻研一些。

当我们输入像 a = 3 + 8 这样的式子时,整数 3 和 8 是怎么知道如何实现 + 的?同样,a 又是怎么知道如何使用=来获取计算结果的?你可以使用 Python 的特殊方法(special method),有时也被称作魔术方法(magic method),来实现这些操作符的功能。

这些特殊方法的名称以双下划线(__)开头和结束。没错,你已经见过其中一个: __init__,它根据类的定义以及传入的参数对新创建的对象进行初始化。

假设你有一个简单的 Word 类,现在想要添加一个 equals() 方法来比较两个词是否一致,忽略大小写。也就是说,一个包含值 'ha' 的 Word 对象与包含 'HA' 的是相同的。

下面的代码是第一次尝试,创建一个普通方法 equals()。self.text 是当前 Word 对象所包含的字符串文本,equals() 方法将该字符串与 word2(另一个 Word 对象)所包含的字符串做比较:

>>> class Word():
...         def __init__(self, text):
...             self.text = text
...
...         def equals(self, word2):
...             return self.text.lower() == word2.text.lower()
...

接着创建三个包含不同字符串的 Word 对象:

>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')

当字符串 'ha' 和 'HA' 被转换为小写形式再进行比较时(我们就是这么做的),它们应该是相等的:

>>> first.equals(second)
True

但字符串 'eh' 无论如何与 'ha' 也不会相等:

>>> first.equals(third)
False

我们成功定义了 equals() 方法来进行小写转换并比较。但试想一下,如果能通过 if first == second 进行比较的话岂不更妙?这样类会更自然,表现得更像一个 Python 内置的类。好的,来试试吧,把前面例子中的 equals() 方法的名称改为 __eq__()

>>> class Word():
...     def __init__(self, text):
...         self.text = text
...     def __eq__(self, word2):
...         return self.text.lower() == word2.text.lower()
...

修改就此结束,来看看新的版本能否正常工作:

>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')
>>> first == second
True
>>> first == third
False

太神奇了!是不是如同魔术一般?仅需将方法名改为 Python 里进行相等比较的特殊方法名 __eq__()即可。表 1 和表 2 列出了最常用的一些魔术方法。

[表1:和比较相关的魔术方法]

方法名 使用
__eq__(self, other) self == other
__ne__(self, other) self != other
__lt__(self, other) self < other
__gt__(self, other) self > other
__le__(self, other) self <= other
__ge__(self, other) self >= other

[表2:和数学相关的魔术方法]

方法名 使用
__add__(self, other) self + other
__sub__(self, other) self - other
__mul__(self, other) self * other
__floordiv__(self, other) self // other
__truediv__(self, other) self / other
__mod__(self, other) self % other
__pow__(self, other) self ** other

不仅数字类型可以使用像 +(魔术方法 __add__())和 -(魔术方法 __sub__())的数学运算符,一些其他的类型也可以使用。例如,Python 的字符串类型使用 + 进行拼接,使用 * 进行复制。其中最常用的一些参见下面的表 3。

[表3:其他种类的魔术方法]

方法名 使用
__str__(self) str(self)
__repr__(self) repr(self)
__len__(self) len(self)

除了 __init__() 外,你会发现在编写类方法时最常用到的是 __str__(),它用于定义如何打印对象信息。交互式解释器则用 __repr__() 方法输出变量。如果在你的类既没有定义 __str__() 也没有定义 __repr__(),Python 会输出类似下面这样的默认字符串:

 >>> first = Word('ha')
>>> first
<__main__.Word object at 0x1006ba3d0>
>>> print(first)
<__main__.Word object at 0x1006ba3d0>

我们将 __str__()__repr__() 方法都添加到 Word 类里,让输出的对象信息变得更好看些:

>>> class Word():
...     def __init__(self, text):
...         self.text = text
...     def __eq__(self, word2):
...         return self.text.lower() == word2.text.lower()
...     def __str__(self):
...         return self.text
...     def __repr__(self):
...         return 'Word("'  self.text  '")'
...
>>> first = Word('ha')
>>> first          # uses __repr__
Word("ha")
>>> print(first)   # uses __str__
ha

参考文档