当我们定义了一个类之后,Python会自动为我们提供一些方法,这些方法大部分是从object类或者type类继承来的。我们可以覆盖这些方法实现特定的操作。
20.1 让对象打印出来更易读
用print()打印对象,会展示__str__()
函数的内容,直接运行实例的时候,打印的对象,会展示__repr__()
函数的内容。因此可以通过实现__str__()
和__repr__()
这两方法,让对象打印出来更符合人类阅读。比如
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self): # print()时调用
return 'Student object (name=%s, age=%d)' % (self.name, self.age)
__repr__ = __str__
print(Student("Jim", 20)) # 打印__str__函数的内容,Student object (name=Jim, age=20)
20.2 让类对象支持迭代
我们知道容器类型都支持for … in… 进行迭代循环。for语句其实做了两件事。第一件事是获得一个迭代器,即调用了__iter__()
函数。第二件事是循环的过程,循环调用__next__()
函数。其实,自定义的类,只要实现了这两个方法,那么类的对象也是可以被for循环迭代的。
比如斐波那契数列,用类实现:
class Fib:
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 返回一个迭代对象,实例本身就是迭代对象,故返回自己
def __next__(self): # for循环时就是调用这个方法获取下一个值
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 10000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
for i in Fib():
if i > 20:
break
print(i)
20.3 让对象可以被调用
我们平时自定义的函数、内置函数和类都属于可调用对象,直观的感受就是可以在他们后面加上一对小括号,但凡是可以把一对括号()应用到某个对象身上,这个对象就可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable()。
callable(0) # 函数返回 False
def add(a, b):
return a + b
callable(add) # 函数返回 True
如果在类中实现了 __call__
方法,那么实例对象也将成为一个可调用对象。即可以实例后跟小括号了,调用后的结果就是__call__
方法的内容。看个例子:
class Student:
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
s = Student('Michael')
s() # 实例变成了Callable的了,输出My name is Michael
这有什么实际用处呢?下面看一个例子,利用__getattr__
和__call__
,实现动态调用,动态生成url字符串。
class Chain:
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
"""调用不存在的属性时,会执行这个方法,方法效果是将path用/拼接起来,返回一个实例"""
return Chain('%s/%s' % (self._path, path))
def __call__(self, path):
"""当实例被调用时,会执行这个方法,方法效果是实例里面的参数也用/拼接起来,返回一个实例"""
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
if __name__ == '__main__':
print(Chain().status.user.timeline.list) # 每一个"."就是一次调用__getattr__方法
print(Chain().users('michael').repos) # Chain().users根据__getattr__的作用会返回一个实例,加上()后回到用__call__方法
20.4 手动创建一个实例对象
__new__
方法负责创建一个实例对象,在对象被创建的时候Python解释器自动调用该方法,它是一个类方法。__new__
方法在返回一个实例之后,Python解释器会继续自动的调用__init__
方法,对实例进行初始化。如果__new__
方法不返回值,或者返回的不是实例,那么Python解释器就不会自动的去调用__init__
方法。__new__
方法主要是当继承一个class时(比如int, str, tuple), 给程序员提供一个自定义这些类的实例化过程的机会。还有就是实现自定义的元类metaclass。
__new__
的一个典型应用是创建单例模式。
单例模式的原理,就是通过在类属性中添加一个单例判定位flag,通过这个flag判断类是否已经被实例化过了,如果类已经被实例化过了就返回实例化的对象。
class Singleton:
# 重写父类的__new__方法,__new__是个类方法,第一个参数是cls,必须有返回值
def __new__(cls, *args, **kwargs): # 自定义类实例化对象的过程
# __new__生成的对象放入cls的_instance属性中, 如果cls不存在_instance
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls, *args, **kwargs) # 调用父类__new__创建实例
# 如果cls存在_instance,直接返回,不要生成新的对象
return cls._instance
class MyClass(Singleton):
pass
s1 = MyClass()
s2 = MyClass()
s3 = MyClass("s3")
s4 = MyClass("s4")
print(id(s1), id(s2), id(s3), id(s4))
继承至Singleton的类,在不重写__new__
的情况下都将是单例模式。
20.5 自定义两个对象比较逻辑
当判断两个对象是否相等时,触发__eq__
方法。可以在这个方法中定义两个对象相等的规则。另外还有__lt__
和__gt__
方法,用来定义两个对象大小比较的规则。例如:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other): # 定义内置方法,自定义两个对象相等的逻辑
return self.__dict__ == other.__dict__ # 两对象空间的属性值都相等,两个对象被认为相等。
def __lt__(self, other):
return self.age < other.age
if __name__ == '__main__':
s1 = Student("李四", 20)
s2 = Student("李四", 20)
print(s2 == s1) # True
print(s2 is s1) # False
s3 = Student("王五", 19)
print(s1 > s3) # True