Python面向对象-特殊成员

我们知道类中有字段、方法和属性三大类成员,并且成员名前如果有两个下划线,则表示该成员是私有成员,私有成员只能由类内部调用。

Python的类成员还存在着一些具有特殊含义的成员,其中有一些比较重要的,我们一一来看:

(1) __init__

构造方法,通过类创建对象时,自动触发执行。

1 class Student(object):
2     def __init__(self, name):
3         self.name = name
4     
5     ...

(2) __del__

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的

1 class Student(object):
2     ...
3 
4     def __del__(self):
5         pass

(3) __call__

我们已经知道了类名后面加()是调用__init__方法,那对象后面加()呢?请看下面的代码:

 1 class Student(object):
 2     def __init__(self, name):
 3         self.name = name
 4 
 5     def __call__(self):
 6         print(self.name)
 7 
 8     ...
 9 
10 mike = Student("Mike")
11 mike()

程序输出是: Mike ,我们可以看到对象()会调用__call__方法,我们可以通过callable()函数判断对象是否可以调用

(4) __dict__

有没有办法获取对象里所有普通字段及其值呢?Python为我们提供了__dict__:

 1 class Student(object):
 2     Nation = "China"
 3 
 4     def __init__(self, name):
 5         self.name = name
 6         self.score = 0
 7 
 8     ...
 9 
10 mike = Student("Mike")
11 print(mike.__dict__)

程序输出: {'score': 0, 'name': 'Mike'} ,对于静态字段Nation = "China",__dict__内并不包含

(5) __str__ , __repr__ 

对于一个对象,我们对其使用print时,默认程序输出的是对象的内存地址:

1 class Student(object):
2     def __init__(self, name):
3         self.name = name
4         self.score = 0
5 
6     ...
7 
8 mike = Student("Mike")
9 print(mike)

程序输出: <__main__.Student object at 0x0050F7B0> 

怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:

 1 class Student(object):
 2     def __init__(self, name):
 3         self.name = name
 4         self.score = 0
 5 
 6     def __str__(self):
 7         return self.name
 8     ...
 9 
10 mike = Student("Mike")
11 print(mike)

程序输出就是__str__方法的返回值: Mike 

如果我们把上面的代码在idle里运行,并敲下mike时(>>>mike),打印出来的还是对象的地址,

这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。

解决办法是再定义一个__repr__()。但是通常__str__()__repr__()代码都是一样的,所以,有个偷懒的写法:

1 class Student(object):
2     def __init__(self, name):
3         self.name = name
4         self.score = 0
5 
6     def __str__(self):
7         return self.name
8     __repr__ = __str__

(6) __iter__

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

 1 class Fib(object):
 2     def __init__(self, maximum):
 3         self.a = 0
 4         self.b = 1  # 初始化两个计数器a,b
 5         self.maximum = maximum # 定义停止循环的条件
 6 
 7     def __iter__(self):
 8         return self  # 实例本身就是迭代对象,故返回自己
 9 
10     # Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到
11     # StopIteration错误时退出循环
12     def __next__(self):
13         self.a, self.b = self.b, self.a + self.b
14         if self.a > self.maximum:
15             raise StopIteration()
16         return self.a
17 
18 for item in Fab(100):
19     print(item)

(7) __getitem__、__setitem__、__delitem__

上面的Fib类的对象虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第4个元素:

1 Fib(100)[4]
2 TypeError: 'Fib' object does not support indexing

要表现得像list那样按照下标取出元素,需要实现__getitem__()方法

1     def __getitem__(self, i):
2         a, b = 0, 1
3         for x in range(i):
4             a, b = b, a + b
5         if a <= self.maximum:
6             return a
7         else:
8             raise ValueError("Out of range")

现在,就可以按下标访问数列的任意一项了: print("Fib(100)[4]:", Fib(100)[4]) ,输出是: Fib(100)[4]: 3 

list还有个神奇的切片方法,Fib该如何实现呢?__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:

 1     def __getitem__(self, i):
 2         if isinstance(i, int):
 3             a, b = 0, 1
 4             for x in range(i):
 5                 a, b = b, a + b
 6             if a <= self.maximum:
 7                 return a
 8             else:
 9                 raise ValueError("Out of range")
10         elif isinstance(i, slice):
11             if isinstance(i, slice):  # i是切片
12                 start = i.start
13                 stop = i.stop
14                 if start is None:
15                     start = 0
16                 a, b = 1, 1
17                 L = []
18                 for x in range(stop):
19                     if x >= start:
20                         L.append(a)
21                     a, b = b, a + b
22                 return L

但是没有对step参数作处理,也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。

此外,如果把对象看成dict__getitem__()的参数也可能是一个可以作key的object,例如str

与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素:

 1     def __getitem__(self, i):
 2         if isinstance(i, int): # 对应于list, tuple的索引
 3             a, b = 0, 1
 4             for x in range(i):
 5                 a, b = b, a + b
 6             if a <= self.maximum:
 7                 return a
 8             else:
 9                 raise ValueError("Out of range")
10         elif isinstance(i, slice): # 对应于list,tuple的切片
11             if isinstance(i, slice):  # i是切片
12                 start = i.start
13                 stop = i.stop
14                 if start is None:
15                     start = 0
16                 a, b = 1, 1
17                 L = []
18                 for x in range(stop):
19                     if x >= start:
20                         L.append(a)
21                     a, b = b, a + b
22                 return L
23         elif isinstance(i, str): # 对应于字典的dic[key]或dic.get[key]
24             print(type(i))
25         else:
26             print("wrong type")
27 
28     def __setitem__(self, key, value): # 对应于字典的dic[key] = value
29         print("key:value", key, value)
30 
31     def __delitem__(self, key): # 对应于del dic[key]
32         print("del", key)

 

posted on 2017-02-14 16:31  _Joshua  阅读(392)  评论(0编辑  收藏  举报

导航