阅读别人编写的Python代码时,经常会在他们定义的类中看到以“__”为开头和结尾的方法,经过进一步学习后才知道类中实现的这类方法被称为“魔术方法”。“魔术方法”在一些情况下会被自动的调用,通过一些简单的定义就可以实现比较神奇的功能。如果你希望根据自己的需求去实现具有“特殊”功能的类,那么就需要对这些方法进行重写。下面内容将对一些常用的“魔术方法”进行介绍。
1、__init__方法
__init__方法功能有点类似于其它面向对象语言中的构造函数,在类实例化对象时它会被自动的调用。一般情况下,在定义的类中都会实现__init__方法,在__init__方法内根据创建类对象时传入参数对类对象的属性进行初始化操作。
class student(object): def __init__(self, _name, _age): self.name = _name self.age = _age def get_info(self): print('%s is %d years old' %(self.name, self.age))
测试结果:
>>> stu = student("Michael", 12) >>> stu.get_info() Michael is 12 years old
2、__new__方法
前面的__init__方法承担了类实例化对象后对类对象属性必要的初始化操作,而__new__方法则是在__init__方法之前被调用创建类对象。与__init__方法不同的是__new__方法必须返回一个值,返回所创建对象的实例。
__new__方法定义形式:
def __new__(cls, *args, **kw): pass
__new__方法的首个参数是cls,因此,它是属于一个类方法,这也是我们可以通过object.__new__来调用它的原因。
不过,一般情况下,在定义的类中很少去重写__new__方法,但在实现单例设计模式时,可通过重写__new__方法实现。
class single(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(single, cls).__new__(cls, *args, **kwargs) return cls._instance def __init(self): pass
上述single类,通过重写了__new__方法,实现了单例设计模式。_instance是single类的属性,用于描述是否已经创建了实例对象。在创建一个single对象时,首先根据类属性_instance判断是否是第一次创建对象,如果是调用父类的__new__方法创建对象,如果不是,就返回之前创建的对象。
测试结果:
>>> obj1 = single() >>> obj2 = single() >>> obj1 <__main__.single object at 0x0000000003D44F08> >>> obj2 <__main__.single object at 0x0000000003D44F08>
3、__call__方法
该方法的功能类似于在类中重载()运算符,使得类实例对象可以像调用普通函数那样,以“类对象名()”形式使用,调用的结果即调用到__call__方法。
class call_cls(object): def __call__(self): print('__call__ function...')
测试结果:
>>> obj = call_cls() >>> obj() __call__ function...
4、__str__方法
如果类中实现了__str__方法,当使用print输出对象时,那么就会打印出从这个方法中返回的数据
class cat(object): def __init__(self, _name, _hobby): self.name = _name self.hobby = _hobby def __str__(self): return ('%s hobby is %s' %(self.name, self.hobby))
测试结果:
>>> Tom = Cat('Tom', 'eat fish') >>> print(Tom) Tom hobby is eat fish
5、__iter__和__next__方法
如果一个类想像list、tuple那样,可以用于for...in循环,那么它就要在内部实现一个__iter__方法,该方法返回一个迭代对象。然后Python的for循环不断的调用该迭代对象的__next__方法来获取循环的下一个值,直到遇到StopIteration异常时退出循环。
现在我们来写一个可用于for...in循环的类,这个类用来计算斐波那契数列
先来看第一种写法:
class Fib_Calc(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __next__(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值 class Fib(object): def __iter__(self): return Fib_Calc()
Fib类中实现__iter__方法,该方法中要返回一个可迭代对象。该对象是Fib_Calc类的实例化,所以,Fib_Calc中还应包含__next__方法
Fib_Calc类中的__next__方法根据斐波那契数列特点,计算一次结果
测试结果:
>>> fib = Fib() >>> for i in fib: print(i) 1 1 2 3 5 8 13 21 34 55 89
再来看第二种写法:
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值
我们可以不像第一种写法那样,直接将__iter__和__next__方法在同一个类中实现。一个类包含__next__方法,这个类就是一个可迭代类,在__iter__方法中只需要返回这个对象自身,那么返回的结果就是一个可迭代对象。
这里同时实现__iter__和__next__方法的对象,也就是我们所说的“迭代器”。
6、__getitem__方法
上面使用__iter__和__next__方法,实现了类能像list和tuple一样使用for...in循环,那么类是否能像list那样按下标取出元素呢?
当然是可以的,如果类要表现得像list那样按照下标取出元素,需要在类中实现__getitem__方法。仍然以斐波拉契数列为例进行演示
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
现在,就可以按下标访问数列的任意一项了:
>>> fib = Fib() >>> fib[2] 2 >>> fib[4] 5
已经实现了下标取数据,但是要像list一样使用切片功能,还需要在__getitem__方法中加入判断。
class Fib(object): def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L
__getitem__()中检测到以切片方式访问时,获取切片开始和结束位置,计算开始至结束位置范围对应的斐波拉契数组值,并将其放入到一个列表中,待循环结束后返回这个列表。
测试结果:
>>> fib = Fib() >>> fib[4] 5 >>> fib[:5] [1, 1, 2, 3, 5]
7、__getattr__方法
一般情况下,如果我们访问到一个对象中不存在的成员属性时,会发生异常
def student(object): def __init__(self, _name): self.name = _name
>>> stu = student('Li Ming') >>> print(stu.name) Li Ming >>> print(stu.age) Traceback (most recent call last): File "<pyshell#19>", line 1, in <module> print(stu.age) AttributeError: 'student' object has no attribute 'age'
很明显,这是因为类中没有age这个属性引起的。除了在类中添加age属性外,Python中还提供了另外一种机制,在类实现__getattr__方法,动态返回一个属性。
>>> stu = student('Li Ming') >>> print(stu.age) 100
小结
这里仅介绍的最常用的几个“魔术方法”,通过例子演示,我们可以看到Python编程时使用“魔术方法”去定制自己类的优势所在。Python中还有很多“魔术方法”可供我们去使用,后续接触到时再对本文的内容进行补充!