第十三章: 面向对象编程
object 是“所有类之母”。如果你的类没有继承任何其他父类,object 将作为默认的父类。它位于所有类继承结构的最上层。
经典类和新式类
经典类又称旧类,是指没有直接或间接继承object类的类
新式类必须直接或间接的继承object类
示例:
class cc: #经典类
def __init__(self):
pass
class ccN(object):#新式类,继承了object类
def __init__(self):
pass
c1=cc()
c2=ccN()
print c1.__class__
print type(c1)
print 'dir(c1):',dir(c1)
print c2.__class__
print type(c2)
print 'dir(c2):',dir(c2)
result:
__main__.cc
<type 'instance'>
dir(c1): ['__doc__', '__init__', '__module__']
<class '__main__.ccN'>
<class '__main__.ccN'>
dir(c2): ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
类中的方法和self参数。
code:
class MyDataWithMethod(object):
def printFoo(self):
print 'something like this'
类中所有的方法必须带有一个self的参数,这个参数代表实例对象本身,当你用实例调用方法时,由解释器悄悄地传递给方法的,所以,你不需要自己传递self 进来,因为它是自动传入的。举例说明一下,假如你有一个带两参数的方法,所有你的调用只需要传递第二个参数,python 把self 作为第一个参数传递进来,如果你犯错的话,也不要紧。Python 将告诉你传入的参数个数有误。总之,你只会犯一次错,下一次———你当然就记得了!
__init__:__init__()类似于类构造器,,Python 创建实例后,在实例化过程中,调用__init__()方法,当一个类被实例化时,就
可以定义额外的行为,比如,设定初始值或者运行一些初步诊断代码———主要是在实例被创建后,
实例化调用返回这个实例之前,去执行某些特定的任务或设置。
我们不把输入(raw_input())或输出(print)语句放入函数中,除非预期代码体具有输出的特性
类和实例,类和子类的关系
类中的属性和方法,当类被实例化后,实例化的对象会自动调用类中的__init__函数,其他的函数属性和方法必须显式的调用。实例调用类中的方法不需要传递self参数,但是当子类调用父类的方法时必须加上self参数。
类和子类的关系,子类可以直接继承父类所有的方法和属性,除非子类中重写的父类的某个方法。
核心笔记:命名类、属性和方法
类名通常由大写字母打头。这是标准惯例,可以帮助你识别类,特别是在实例化过程中(有时看起来像函数调用)。还有,数据属性(译者注:变量或常量)听起来应当是数据值的名字,方法名应当指出对应对象或值的行为。另一种表达方式是:数据值应该使用名词作为名字,方法使用谓词(动加对象)。数据项是操作的对象、方法应当表明程序员想要在对象进行什么操作。在上面我们定义的类中,遵循了这样的方针,数据值像“name”phone”和“email”,行为如“updatePhone”,“updateEmail”。这就是常说的“混合记法(mixedCase)”或“骆驼记法(camelCase)。。Python 规范推荐使用骆驼记法的下划线方式,比如,“update_phone”,“update_email”。类也要细致命名,像“AddrBookEntry”,“RepairShop”等等就是很好的名字
封装/接口
类方法只能有类的实例来调用,类本身无法调用。
决定类的属性:
有两种方法。最简单的是使用dir()内建函数。另外是通过访问类的字典属性__dict__,这是所有类都具备的特殊属性之
class MyClass(object):
'''MyClass class definition
'''
myVersion=1.1
def MyNoActionMethod(self):
pass
dir(MyClass)结果:
['MyNoActionMethod',
'__class__',
'__delattr__',
'__dict__',
'__doc__',
'__format__',
'__getattribute__',
'__hash__',
'__init__',
'__module__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__', 'myVersion']
dir()返回的仅是对象的属性的一个名字列表,而__dict__返回的是一个字典,它的键(keys)是属性名,键值(values)是相应的属性对象的数据值。
特殊的类属性:
C.__name__ 类C的名字(字符串)
C.__doc__ 类C的文档字符串
C.__bases__ 类C的所有父类构成的元组
C.__dict__ 类C的属性
C.__module__ 类C定义所在的模块(1.5 版本新增)
C.__class__ 实例C对应的类(仅新式类中)
1.print MyClass.__name__: MyClass
2.print MyClass.__doc__: MyClass class definition
3.print MyClass.__bases__: (<type 'object'>,)
4.print MyClass.__dict__: 返回这个类的所有属性和属性值得一个字典
5.print MyClass.__module__: '__main__'
6.print MyClass.__class__: <type 'type'>
__doc__,__dict__及__module__是所有类都具备的特殊属性。
1.__name__ 获得类或者内建类型的名字
例如:b=type(3) b.__name__ 我们会获得一个int
question:为什么我用dir(MyClass)和MyClass.__dict__都没有查看到类的__name__属性,却可以通过C.__name__ 获得类的名字呢?
2.__doc__是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line)
后的字符串。文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串
3.返回该类的所有的父类
4.前述的__dict__属性包含一个字典,由类的数据属性组成。
不是所有的对象都有__dict__属性。例如,如果你在一个类中添加了__slots__属性,那么这个类的实例将不会拥有__dict__属性,但是dir()仍然可以找到并列出它的实例所有有效属性,许多内建类型没有__dict__属性,Python的实例拥有它们自己的__dict__,而它们对应的类也有自己的__dict__:
5.该类所属于的模块名
类型和类的统一,type(class)->result;<type 'type'>, type(int)->result:<type 'type'>
__init__() "构造器"方法
当类被调用去实例化一个对象时,首先检查类中是否有__init__方法,如果有调用,并且传进来的所有参赛都给了此方法, 如果没有实现此方法只什么都不做返回一个实例对象给程序员。你可以想像成这样:把创建实例的调用当成是对构造器的调用。
_new__()。这个方法还没见过,先放着。。。
__del__().删除实例
实例属性:
实例只有数据属性 ,可以通过(instance.__dict__)来获得实例属性,他是没有方法属性的,而且数据属性通过实例加上dot加上数据属性名字,来给某个特定的实例添加属性,这个数据属性跟类和其他的实例是没有任何关联的。
__init__()应当返回None,如果定义了构造器,它不应当返回任何对象,因为实例对象是自动在实例化调用后返回的。相
应地,__init__()就不应当返回任何对象(应当为None);否则,就可能出现冲突,因为只能返回实
例。试着返回非None 的任何其它对象都会导致TypeError 异常
内建类型中没有__dict__属性。
实例属性 vs 类属性
实例属性:。你可采用类来访问类属性,如果实例没有同名的属性的话,你也可以用实例来访问。
雷属性:类属性一般通过类名加上dot加上属性名去修改其值,实例名是无法修改类属性的,但是这个只限制类属性是不可改变的,如果累属性是list,或者dict,则既可以通过类型去修改类属性又可以通过实例名去修改类属性。
class Foo(object):
x=[1,2,3]
foo=Foo()
print foo.x
foo.x.append(5)
print Foo.x
这个里面foo.x.append(5) 和Foo.x.append(5)效果是一样的。
总的来说我们建议使用类名来修改类属性,而不用去区分类属性是可变还是不可变的了。
Python 中绑定(binding)的概念。
方法仅仅是类内部定义的函数,方法只有在其所属的类拥有实例时才能被而且仅能被‘实例’调用,当存在一个实例时方法被认为是绑定到那个实例的,没有实例时方法就没有绑定的。最后任何一个方法定义中的第一个参数都是变量self,他表示调用此方法的实例对象。
这个也有一个例外,比如我们有一个子类,子类中需要重写父类的中的方法,这个时候父类并没有实例,但是我们在子类中用父类的名字加点加方法名去调用了父类的一个方法了,这个是唯一一种没有实例化而方法被调用的时候。
核心笔记:self 是什么?
self 变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是
作为第一个参数传递的,self 被选中用来代表实例。你必须在方法声明中放上self(你可能已经注
意到了这点),但可以在方法中不使用实例(self)。如果你的方法中没有用到self , 那么请考虑创建
一个常规函数,除非你有特别的原因。毕竟,你的方法代码没有使用实例,没有与类关联其功能,
这使得它看起来更像一个常规函数。在其它面向对象语言中,self 可能被称为 this。
绑定的方法被调用的时候就不需要再传递self参数了,因为在写这个方法的时候你已经把self传递进去了,没有绑定的方法被调用的时候就需要传递self参数,比如子类重写父类的方法的时候要调用父类的方法要写__init__(self),或者是super(subclass,self).__init__()
静态方法和类方法。
要想在类中定义不带self参数的方法,必须要声明这个方法是静态方法或者类方法。
例子:
静态方法:
class TestStaticMethod:
def foo():
print 'calling static method foo()'
foo=staticmethod(foo)#等同于@staticmethod ,不过要放到foo()方法代码上一行去。
tsm=TestStaticMethod()#this is a static method.
tsm.foo()
类方法:
class TestClassMethod:
#@classmethod
def foo(cls):
print 'foo() is part of class:',cls.__name__
foo=classmethod(foo)
tsm=TestClassMethod()
tsm.foo()
使用函数修饰符:见上面的例子。
使用别人写的类有两种方法,组合,和派生。
核心笔记:重写__init__不会自动调用基类的__init__
类似于上面的覆盖非特殊方法,当从一个带构造器 __init()__的类派生,如果你不去覆盖
__init__(),它将会被继承并自动调用。但如果你在子类中覆盖了__init__(),子类被调用
2.7版本后都可以对标准类型子类化了,比如子类化int,float、dict等。
多重继承:当使用多重继承时,有两个不同的方面要记住。首先,还是要找到合适的属性。另一个就是当你重写方法时,如何调用对应父类方
法以“发挥他们的作用”,同时,在子类中处理好自己的义务。
方法解释顺序(MRO):使用广度优先。
新式类也有一个__mro__属性,告诉你查找顺序是怎样的:
>>> GC.__mro__
(<class '__main__.GC'>, <class '__main__.C1'>, <class'__main__.C2'>, <class '__main__.P1'>, <class'__main__.P2'>, <type 'object'>)
总结
经典类,使用深度优先算法。因为新式类继承自object,新的菱形类继承结构出现,问题也就
接着而来了,所以必须新建一个MRO。
类、实例和其他对象的内建函数
issubclass() 布尔函数判断一个类是另一个类的子类或子孙类。它有如下语法:issubclass(sub, sup),从Python 2.3 开始,issubclass()的第二个参数可以是可能的父类组成的tuple(元组),这时,只要第一个参数是给定元组中任何一个候选类的子类时,就会返回True。
isinstance()布尔函数在判定一个对象是否是另一个给定类的实例时,非常有用。它有如下语法:
isinstance(obj1, obj2)
isinstance()在obj1 是类obj2 的一个实例,或者是obj2 的子类的一个实例时,返回True(反之,则为False)。
hasattr(), getattr(),setattr(), delattr()
*attr()系列函数可以在各种对象下工作,不限类与实例,但是在类与实例中用的很频繁,第一个参数是你要处理的对象,第二个参数是这些属性的字符串名字。
hasattr()函数是Boolean 型的。,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先作一下检查。
getattr()和setattr()函数相应地取得和赋值给对象的属性,getattr()会在你试图读取一个不存在的属性时,引发AttributeError 异常,除非给出那个可选的默认参数。setattr()将要么加入一个新的属性,要么取代一个已存在的属性。
delattr()函数会从一个对象中删除属性。
示例:
class myClass(object):
def __init__(self):
self.foo=100
>>> myInst=myClass()
>>> hasattr(myInst,'foo')
True
>>> getattr(myInst,'foo')
100
>>> setattr(myInst,'bar','my attr')# same as myInst.bar='my attr'
>>> dir(myInst)
>>> hasattr(myInst,'bar')
True
>>> getattr(myInst,'bar') # same as myInst.bar
'my attr'
>>> delattr(myInst,'foo')
>>> hasattr(myInst,'foo')
False
>>> dir(myInst)
用dir()列出一个模块所有属性的信息。
?? dir()作用在实例上(经典类或新式类)时,显示实例变量,还有在实例所在的类及所有它的基类中定义的方法和类属性。
?? dir()作用在类上(经典类或新式类)时,则显示类以及它的所有基类的__dict__中的内容。但它不会显示定义在元类(metaclass)中的类属性,即:dir(myClass)==myClass.__dict__+ object.__dict__
?? dir()作用在模块上时,则显示模块的__dict__的内容。(这没改动)。
?? dir()不带参数时,则显示调用者的局部变量。(也没改动)。
super():
语法如下:super(type[, obj])
给出type,super()“返回此type 的父类”。如果你希望父类被绑定,你可以传入obj 参数(obj必须是type 类型的).否则父类不会被绑定。obj 参数也可以是一个类型,但它应当是type 的一个子类。通常,当给出obj 时:
?? 如果 obj 是一个实例,isinstance(obj,type)就必须返回True
?? 如果 obj 是一个类或类型,issubclass(obj,type)就必须返回True
vars():
vars()内建函数与dir()相似,只是给定的对象参数都必须有一个__dict__属性。vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值。
实例及其它对象的内建函数
内建函数 描述
issubclass(sub, sup) 如果类sub 是类sup 的子类,则返回True,反之,为False。
isinstance(obj1, obj2) 如果实例obj1 是类obj2 或者obj2 子类的一个实例;或者如果obj1是obj2 的类型,则返回True;反之,为 False。
hasattr(obj, attr) 如果obj 有属性attr(用字符串给出),返回True,反之,返回表13.3 类,实例及其它对象的内建函数( 续)
内建函数 描述
getattr(obj, attr[, default]) 获取obj 的attr 属性;与返回obj.attr 类似;如果attr不是obj 的属性,如果提供了默认值,则返回默认 值;不然,就会引发一个AttributeError 异常。
setattr(obj, attr, val) 设置obj 的attr 属性值为val,替换任何已存在的属性值;不然,就创建属性;类似于obj.attr=val
delattr(obj, attr) 从obj 中删除属性attr(以字符串给出);类似于del obj.attr。
dir(obj=None) 返回obj 的属性的一个列表;如果没有给定obj,dir()则显示局部名字空间空间中的属性,也就是locals ().keys()
super(type, obj=None) 返回一个表示父类类型的代理对象;如果没有传入obj,则返 回的super 对象是非绑定的;反之,如果obj 是一个type , issubclass(obj,type) 必为True ; 否则,isinstance(obj,type)就必为True。
vars(obj=None) 返回obj 的属性及其值的一个字典;如果没有给出obj,vars()显示局部名字空间字典(属性及其值),也 就是locals()。
用特殊方法定制类:
特殊方法 描述
基本定制型
C.__init__(self[, arg1, ...]) 构造器(带一些可选的参数)
C.__new__(self[, arg1, ...]) 构造器(带一些可选的参数);通常用在设置不变数据类型的子类。
C.__del__(self) 解构器
C.__str__(self) 可打印的字符输出;内建str()及print 语句
C.__repr__(self) 运行时的字符串输出;内建repr() 和‘‘ 操作符
C.__unicode__(self) Unicode 字符串输出;内建unicode()
C.__call__(self, *args) 表示可调用的实例
C.__nonzero__(self) 为object 定义False 值;内建bool() (从2.2 版开始)
C.__len__(self) “长度”(可用于类);内建len()
特殊方法 描述
对象(值)比较c
C.__cmp__(self, obj) 对象比较;内建cmp()
C.__lt__(self, obj) and 小于/小于或等于;对应<及<=操作符
C.__gt__(self, obj) and 大于/大于或等于;对应>及>=操作符
C.__eq__(self, obj) and 等于/不等于;对应==,!=及<>操作符
属性
C.__getattr__(self, attr) 获取属性;内建getattr();仅当属性没有找到时调用
C.__setattr__(self, attr, val) 设置属性
C.__delattr__(self, attr) 删除属性
C.__getattribute__(self, attr) a 获取属性;内建getattr();总是被调用
C.__get__(self, attr) (描述符)获取属性
C.__set__(self, attr, val) (描述符)设置属性
C.__delete__(self, attr) (描述符)删除属性
定制类/模拟类型
数值类型:二进制操作符
C.__*add__(self, obj) 加;+操作符
C.__*sub__(self, obj) 减;-操作符
C.__*mul__(self, obj) 乘;*操作符
C.__*div__(self, obj) 除;/操作符
C.__*truediv__(self, obj) e True 除;/操作符
C.__*floordiv__(self, obj) e Floor 除;//操作符
C.__*mod__(self, obj) 取模/取余;%操作符
C.__*divmod__(self, obj) 除和取模;内建divmod()
C.__*pow__(self, obj[, mod]) 乘幂;内建pow();**操作符
C.__*lshift__(self, obj) 左移位;<<操作符
特殊方法 描述
定制类/模拟类型
数值类型:二进制操作符
C.__*rshift__(self, obj) 右移;>>操作符
C.__*and__(self, obj) 按位与;&操作符
C.__*or__(self, obj) 按位或;|操作符
C.__*xor__(self, obj) 按位与或;^操作符
数值类型:一元操作符
C.__neg__(self) 一元负
C.__pos__(self) 一元正
C.__abs__(self) 绝对值;内建abs()
C.__invert__(self) 按位求反;~操作符
数值类型:数值转换
C.__complex__(self, com) 转为complex(复数);内建complex()
C.__int__(self) 转为int;内建int()
C.__long__(self) 转为long;内建long()
C.__float__(self) 转为float;内建float()
数值类型:基本表示法(String)
C.__oct__(self) 八进制表示;内建oct()
C.__hex__(self) 十六进制表示;内建hex()
数值类型:数值压缩
C.__coerce__(self, num) 压缩成同样的数值类型;内建coerce()
C.__index__(self)g 在有必要时,压缩可选的数值类型为整型(比如:用于切片
索引等等)
----------------------------------------
序列类型
C.__len__(self) 序列中项的数目
C.__getitem__(self, ind) 得到单个序列元素
C.__setitem__(self, ind,val) 设置单个序列元素
C.__delitem__(self, ind) 删除单个序列元素
特殊方法 描述
序列类型
C.__getslice__(self, ind1,ind2) 得到序列片断
C.__setslice__(self, i1, i2,val) 设置序列片断
C.__delslice__(self, ind1,ind2) 删除序列片断
C.__contains__(self, val) f 测试序列成员;内建in 关键字
C.__*add__(self,obj) 串连;+操作符
C.__*mul__(self,obj) 重复;*操作符
C.__iter__(self) e 创建迭代类;内建iter()
映射类型
C.__len__(self) mapping 中的项的数目
C.__hash__(self) 散列(hash)函数值
C.__getitem__(self,key) 得到给定键(key)的值
C.__setitem__(self,key,val) 设置给定键(key)的值
C.__delitem__(self,key) 删除给定键(key)的值
C.__missing__(self,key) 给定键如果不存在字典中,则提供一个默认值
示例:
class Time60(object):
def __init__(self,hr,min):
self.hr=hr
self.min=min
def __str__(self):
return '%d:%d'%(self.hr,self.min)#字符串化输出
def __add__(self,other):
return self.__class__(self.hr+other.hr,self.min+other.min) #加法运算
__repr__=__str__
def __iadd__(self,other):
self.hr+=other.hr
self.min+=other.min
return self #原位加法运算,即time1经过+=后的id(time1)不变
time1=Time60(3,45)
time2=Time60(4,33)
print time1
print id(time1)
time1+=time2
print id(time1)
print time1
重载
class NumStr(object):
def __init__(self,num=0,string=''):
self.__num=num
self.__string=string
def __str__(self):
return '[%d::%r]'%(self.__num,self.__string)
#__repr__=__str__
def __add__(self,other):
if isinstance(other,NumStr):
return self.__class__(self.__num+other.__num,self.__string+other.__string)
else:
raise TypeError,'Illegal argument type for built-in operation'
def __mul__(self,num):
if isinstance(num,int):
return self.__class__(self.__num*num,self.__string*num)
else:
raise TypeError,'illegal argument type for built-in operation'
def __nonzero__(self):
return self.__num or len(self.__string)
def __norm_cval(self,cmpres):
return cmp(cmpres,0)
def __cmp__(self,other):
return self.__norm_cval(cmp(self.__num,other.__num))+self.__norm_cval(cmp(self.__string,other.__string))
a = NumStr(3, 'foo')
b = NumStr(3, 'goo')
c = NumStr(2, 'foo')
d = NumStr()
e = NumStr(string='boo')
f = NumStr(1)
print a*6