【循序渐进学Python】8.面向对象的核心——类型(下)

1 构造和初始化对象

__init__方法是Python内建众多魔法方法(什么是魔法方法?)中最常见的一个,通过这个方法我们可以定义一个对象的初始操作。当构造函数被调用的时候的任何参数都会传递给__init__方法,然后该方法根据这些参数进行对象的初始化工作:

 # -- coding: utf-8 --
class Employee(object):
def __init__(self,name):
self.name = name e = Employee('Sunshine') print e.name # Sunshine

__init__方法对应的是__del__析构方法,在对象被垃圾回收前调用,除了在进行套接字、文件IO这些非托管资源操作外,一般情况下很少会用到它。

1.1 正确的初始化子类型

重写是继承机制中一个重要的内容,对于构造方法尤其如此。大多数类型的子类既要初始化自己的部分,也要调用基类的构造方法,因为保证了对象被正确的初始化,如下所示:

 # -- coding: utf-8 --
class Bird(object):
def __init__(self):
self.hungry = True def eat(self):
if self.hungry:
print 'Aaaah...'
self.hungry = False
else:
print 'No. thanks' class SongBird(Bird):
def __init__(self):
self.sound = 'Squawk!' def sing(self):
print self.sound # AttributeError: 'SongBird' object has no attribute 'hungry'
sb = SongBird()
sb.eat()

SongBird这个子类在调用继承于父类Birdeat()方法时由于其自身的构造函数没有正确的初始化基类的成员hungry所有就会抛出AttributeError的异常。正确初始化父类成员的方法有两个:

1.直接调用基类构造方法。这样修改SongBird类型的定义即可:

 class SongBird(Bird):
def __init__(self):
Bird.__init__(self) # 调用基类构造方法
self.sound = 'Squawk!' def sing(self):
print self.sound

2.调用super函数,可以这样修改SongBird类型的定义:

 class SongBird(Bird):
def __init__(self):
super(SongBird,self).__init__() # 使用super函数
self.sound = 'Squawk!' def sing(self):
print self.sound

一般来讲,使用第一种方式:调用未绑定的基类构造方法,是为了兼容旧的代码,如果不需要兼容旧版本的代码还是推荐使用super函数来初始化基类的成员。从上面的示例可能觉得二者差别不大,但是为什么还要出现super函数呢,因为super函数(其他它是一个类)主要是用来解决多重继承问题的,因为这牵涉到顺序查找(MRO)、重复调用等等一系列的复杂问题。为了保证我们代码的一致性,我们应该同时只使用一种风格来编写代码。

2. 魔法方法

在Python中内建了很多"magic"方法这些方法和其他函数有些细微的不同:

  1. 从形式上来说名字都是以双下划线(__)来开始和结束的(如:__new__)
  2. 大部分的魔法方法会在特定的情况下被Python自动调用。(比如__init__方法会在对象被创建后自动调用)

2.1 通过魔法方法定制类型

使用Python魔法方法最大的优势在于可以创建出自己的拥有和内置类型同样行为的类出来。例如我们可以通过重写下面的魔法方法,来为自己的类型定制自己的比较规则:

  • __eq__(self,other) —— 定义了等于的行为(=)
  • __ne__(self,other) —— 定义了不等于的行为(!=)
  • __lt__(self,other) —— 定义了小于的行为(<)
  • __gt__(self,other) —— 定义了大于的行为(>)
  • __le__(self,other) —— 定义了小于等于的行为(<=)
  • __ge__(self,other) —— 定义了大于等于的行为(>=)

例如,我们可以重写定义类型的==等行为,只需要重写__eq__方法即可:

 # -- coding: utf-8 --
class Employee(object):
def __init__(self,name):
self.name = name def __eq__(self,other):
return self.name == other e = Employee('Sun')
print e == 'Sun' # True

Python内置的魔法方法中除了这些用于比较的魔法方法之外,还有关于操作符、类型转换以及序列相关的等等一系列的魔法方法。如果想了解更多,这里有一份很好的文档(注意文档中错别字):Python魔法方法指南

3. 属性

在Python中通过访问器定义的特性被称为属性(property)。有两种定义属性的方式,首先我们来使用property函数来创建属性:

 # --coding:utf-8--
class Employee(object):
def __init__(self):
self.__name = None # set 访问器
def getName(self):
return self.__name # get 访问器
def setName(self,value):
self.__name = value # 析构函数
def delName(self):
del self.__name name = property(getName,setName,delName,'set and get Name property') c = Employee()
c.name = "Bob"
print c.name # output: Bob

另一种方式就是Python 2.6 新增的语法,get/set/del函数都是使用的同一个名字:

 # --coding:utf-8--
class Employee(object):
def __init__(self):
self.__name = None def name(self):
return self.__name def name(self,value):
self.__name=value def name(self):
del self.__name c = Employee()
c.name = "jobs"
print c.name # jobs

3.1 控制属性访问

如果需要加强对属性的控制,可以使用Python为我们提供一系列的魔法方法:

  • __getattribute__(self,name):当特性name被访问时自动被调用(只能新式类中使用)
  • __getattr__(self,name):当特性name被访问且对象没有相应的特性时被自动调用
  • __setattr__(self,name):给特性name赋值时会被自动调用
  • __delattr__(self,name):当删除特性name时被自动调用

我们知道每当属性被赋值的时候,__setattr__()就会被调用,通过它和__dict__[]的组合,我们可以通过动态为一个类创建创建特性,如下:

 # -- coding: utf-8 --
class Employee(object):
def __init__(self,name):
self.name = name # RuntimeError: maximum recursion depth exceeded
# #每当属性被赋值的时候,__setattr__()会被调用,这样就造成了递归调用。
# def __setattr__(self,name,value):
# self.name = value def __setattr__(self,name,value):
self.__dict__[name] = value e = Employee("sunshine")
print e.name
e.email = "sunshine@gmail.com"
print e.email

如果只能访问特定特性的话其实也很简单,只需要想下面 __setattr__中加个if判断即可:

 # -- coding: utf-8 --
class AccessCounter(object):
'''一个包含计数器的控制权限的类每当值被改变时计数器会加一''' def __init__(self, val):
super(AccessCounter, self).__setattr__('counter', 0)
super(AccessCounter, self).__setattr__('value', val) def __setattr__(self, name, value):
if name == 'value':
# 通过调用基类的__setattr__方法可以防止递归
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
# 如果不想让其他属性被访问的话,那么可以抛出 AttributeError(name) 异常
super(AccessCounter, self).__setattr__(name, value) def __delattr__(self, name):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
super(AccessCounter, self).__delattr__(name) c = AccessCounter(10)
c.value = 1
c.value = 2
c.value = 3
c.value = 4
c.value = 5
print c.counter # value特性的值改变了5次

4. 定义静态方法和类成员方法

静态方法和类成员方法分别在创建时被装入Staficmethod类型和Classmethod类型的对象中。静态方法定义没有任何参数,能够被类本身直接调用。类方法在定义时需要名为cls的参数(类似于self参数),且能够被类本身调用。Python 2.4 为我们提供了名为:装饰器(decorators)的新语法,可以很简单的创建这两种方法:

 class MyClass(object):

     @staticmethod
def smeth():
print 'This is a static method' @classmethod
def cmeth(cls):
print 'This is a class method of',cls MyClass.smeth() # This is a static method
MyClass.cmeth() # This is a class method of <class '__main__.MyClass'>
my = MyClass()
my.cmeth() # This is a class method of <class '__main__.MyClass'>
my.smeth() # This is a static method

5. 协议

协议(protocol)在Python中的概念和C#中的接口比较类似,协议说明了应该实现何种方法和这些方法应该做什么。比如说,如果想要自定义一个序列,那么只需要遵守序列的协议即可。

5.1 迭代器

迭代器必须遵循迭代器协议,需要有 __iter__ (返回它本身) 和 next。__iter__方法是迭代器规则(iterator protocol)的基础。__iter__返回一个具有next方法的迭代器(iterator)对象,调用next方法时,迭代器会返回它的下一个值,直到没有值可返回时才会引发一个StopIteration异常。下面的示例使用迭代器来创建斐波那契数列,如下:

 class Fibs(object):
def __init__(self):
self.a = 0
self.b = 1 def next(self):
self.a,self.b = self.b,self.a+self.b
return self.a def __iter__(self):
return self fibs = Fibs() # output: 1 1 2 3 5 8 13 21 34 55
for f in fibs:
if f < 60:
print f,
else:
break

我们可以借助内建函数iter从可迭代的对象中获得迭代器:

 it = iter([1,2,3])
print it.next() #
print it.next() #
print it.next() #

也可以将迭代器转换为一个序列:

 # -- coding: utf-8 --
class TestIterator(object):
value = 0
def next(self):
self.value += 1
if self.value >10:raise StopIteration
return self.value def __iter__(self):
return self ti = TestIterator() # 通过list构造方法显示将迭代器转换为列表
print list(ti)

6. 生成器

生成器是用一种普通的函数语法定义的迭代器。任何包含yield语句的函数都可以称为生成器。当包含yield语句的函数被调用时,函数体中的代码不会被执行,而是返回一个迭代器对象。每次请求一个值,就会执行生成器中的代码,知道遇到一个yield或者return语句。如下:

 # -- coding: utf-8 --
nested = [[1,2],[3,4],[5]] def flatten(nested):
for sublist in nested:
for element in sublist:
yield element # <generator object flatten at 0x0000000001D90168>
print flatten(nested) # 调用时才真正执行
for num in flatten(nested):
print num, # 1 2 3 4 5

参考资料&进一步阅读

Python 魔法方法指南

Python 描述符简介

Python基础教程(第二版)

深刻理解Python中的元类(metaclass)

上一篇:opencv学习之路(35)、SURF特征点提取与匹配(三)


下一篇:利用tcpdump抓包工具监控TCP连接的三次握手和断开连接的四次挥手