__str__(), __add__(), __sub__(), __mul__(), __truediv__(), __len__(),
__new__(), __init__(), __del__(), __repr__(), __bytes__(), __format__(),
__lt__(), __le__(), __eq__(), __ne__(), __gt__(), __ge__(), __hash__(),
__bool__(), __dir__(), __set__(), __call__(), __slots__(), ...
对于Python的内建对象,比如int、dict、list等,通过str()方法,可以把这些对象转换为字符串对象输出。
num = 12
str(num) # ==> '12'
d = {1: 1, 2: 2}
str(d) # ==> '{1: 1, 2: 2}'
l = [1,2,3,4,5]
str(l) # ==> '[1, 2, 3, 4, 5]'
对于自定义对象,通过str()方法,同样可以得到对象所对应的字符串结果,只不过结果会有些难理解。
class Person:
pass
bob = Person()
str(bob) # ==> '<__main__.Person object at 0x7fc77b859c50>'
<__main__.Person object at 0x7fc77b859c50>
这个结果其实是Animal的实例cat在内存中的地址,这是相当难以理解的,不过引发思考的是,通过str()打印的数据,是怎么来的呢?
这其实是对象的内建方法__str__
返回的。
通过dir()方法,我们可以把对象的所有方法打印出来。
>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
可以看到,int、dict、list等的内建对象都实现了自己的__str__()
方法,可以把相应的字符串返回,如果我们的类也想把容易理解的字符串输出的话,那么我们也需要实现类的__str__()
方法。
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return 'name: {}, gender: {}'.format(self.name, self.gender)
bob = Person('Bob', 'Male')
str(bob) # ==> 'name: Bob, gender: Male'
但是,对于直接在终端输入变量bob,得到的依然是这样结果。
>>> bob
<__main__.Person object at 0x7fc77b859cc0>
而对于int、list等的对象,直接输入变量也可得到可读的结果。
>>> num = 12
>>> str(num)
'12'
>>> d = {1: 1, 2: 2}
>>> d
{1: 1, 2: 2}
__str__()函数似乎没有在自定义类Person中生效,这是为什么呢?
这是因为 Python 定义了__str()__
和__repr__()
两种方法,__str()__
用于显示给用户,而__repr__()
用于显示给开发人员,当使用str()时,实际调用的是__str__()
方法,而直接输入变量,调用的是__repr__()
方法。
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return 'name: {}, gender: {}'.format(self.name, self.gender)
def __repr__(self):
return 'name: {}, gender: {}'.format(self.name, self.gender)
bob = Person('Bob', 'Male')
str(bob) # ==> 'name: Bob, gender: Male'
>>> bob
'name: Bob, gender: Male'
对于列表List或者元组Tuple,通过内建方法len(),可以得出列表或者元组中元素的个数。如果一个类表现得像一个list,想使用len()函数来获取元素个数时,则需要实现len()方法。
比如我们实现一个班级Class的类,初始化把班级的同学名字列表传进去,希望len()函数可以返回班级同学的数量时,可以这样实现。
class Class:
def __init__(self, students):
self.students = students
def __len__(self):
return len(self.students)
students = ['Alice', 'Bob', 'Candy']
class_ = Class(students)
len(class_) # ==> 3
通过自定义__len__()
方法,可以让len()
函数返回相关的结果,如果没有定义__len__()
方法的类使用len()
函数获取长度时,将会引起异常。
class Class:
def __init__(self, students):
self.students = students
class_ = Class(students)
len(class_)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'Class' has no len()
事实上,Python很多的操作都是通过内建函数来实现的,比如最熟悉的加减乘除,都是通过内建函数来实现的,分别是__add__、__sub__、__mul__、__truediv__
。因此,只要我们的自定义类实现了相关的内建函数,我们的类对象,也可以做到加减乘除。
对于有理数,我们可以使用Rational类来表示:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
其中,p、q 都是整数,表示有理数 p/q。
如果要让Rational进行加法运算,需要正确实现__add__
:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __add__(self, r):
return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
def __str__(self):
return '{}/{}'.format(self.p, self.q)
定义好后,就可以尝试一下有理数的加法了:
>>> r1 = Rational(1, 2)
>>> r2 = Rational(2, 3)
>>> print(r1 + r2)
7/6
需要注意__add__()
函数,它有一个参数,表示的是运算的第二个操作数,比如:r1 + r2,那么在add()方法中的参数,r指的就是r2,这个参数是运算符重载的时候传递的。
另外,细心的同学可能注意到了,相比加减乘的特殊方法,除法的特殊方法名字较长__truediv__
,并且含有true这样的描述,这其实和Python除法是有关系的。
Python的除法可以分为地板除(你没看错,就是地板)和普通除法,地板除的特殊方法是__floordiv__
,普通除法是__truediv__
。
地板除法和普通除法不一样,地板除法的结果只会向下取整数。
>>> num = 5
>>> num.__truediv__(3)
1.6666666666666667
>>> num.__floordiv__(3)
1 # 向下取整
>>> num = 7
>>> num.__floordiv__(3)
2
在运算中,普通除法使用/
表示,而地板除使用//
表示。
>>> 5 / 3
1.6666666666666667
>>> 5 // 3
1
由于Python是动态语言,任何实例在运行期都可以动态地添加属性。比如:
class Student(object):
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
此时,Student类有三个属性,name、gender、score,由于是动态语言,在运行时,可以随意添加属性。
student = Student('Bob', 'Male', 99)
student.age = 12 # ==> 动态添加年龄age属性
如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的__slots__
来实现。
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
使用__slots__ = ('name', 'gender', 'score')
限定Student类的属性,这个时候在外部再次添加动态属性age,将会报错。
student = Student('Bob', 'Male', 99)
>>> student.age = 12 # ==> 动态添加年龄age属性
Traceback (most recent call last):
AttributeError: 'Student' object has no attribute 'age'
__slots__
的目的是限制当前类所能拥有的属性,避免因为外部属性的操作导致类属性越来越难以管理。
在Python中,函数其实是一个对象,我们可以将一个函数赋值给一个变量,而不改变函数的功能。
>>> f = abs
>>> f
<built-in function abs>
>>> abs
<built-in function abs>
>>> f.__name__
'abs'
>>> f(-123)
123
把内建函数abs()赋值给变量f之后,可以看到f就和abs一样,都是。 由于 f 可以被调用,所以,f 被称为可调用对象,而事实上,所有的函数都是可调用对象。
如果把一个类实例也变成一个可调用对象,可以实现一个特殊的方法__call__()
。
例如,我们把Person类变成一个可调用对象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print('My name is {}...'.format(self.name))
print('My friend is {}...'.format(friend))
接着我们初始化一个Person对象,并对这个对象通过函数的方式调用:
>>> p = Person('Bob', 'Male')
>>> p('Alice') # ==> 用函数的方式调用Person类的实例p
My name is Bob...
My friend is Alice...