Python核心编程读笔 12:OOP
第13章 面向对象编程
一、基本概念
1、object类是所有类的基类,如果你的类没有继承任何其他父类,object 将作为默认的父类。
2、python创建实例时无需new:
myFirstObject = MyNewObjectType() #“函数调用”形式!!!
3、python类的所有非静态方法的第一个形参都是self
4、python创建类时的继承:
class EmplAddrBookEntry(AddrBookEntry): #括弧内的便是基类
……
5、python中所有的类属性均public,但名字可能被“混淆”以阻止未经授权的访问,仅此而已!
6、python中的OOP术语
抽象/实现
封装/接口
合成
派生/继承/继承结构
泛化/特化
多态
自省/反射:
该性质展示了某对象是如何在运行期取得自身信息的。即如果传一个对象给你,你可以查出它有什么能力。
python中的type() dir()等内建函数都使用了反射机制
二、类
1 类
(1)创建类
class ClassName( bases ):
'class documentation string' #'类文档字符串'
class_suite #类体
(2)类的属性
属性 = 数据属性 + 方法属性
特殊的类属性:
C.__name__ 类C的名字(字符串)
C.__doc__ 类C的文档字符串
C.__bases__ 类C的所有父类构成的元组
C.__dict__ 类C的属性
C.__module__ 类C定义所在的模块(1.5 版本新增)
C.__class__ 实例C对应的类(仅新式类中)
2 实例
(1)关于__init__()和__del__()方法
不要忘记首先调用父类的__init__()和__del__()
调用del x不表示调用了x.__del__(),其仅是减少x的引用计数,只有当引用计数为1时才会执行__del__()函数
除非你知道你正在干什么,否则不要去实现__del__()
(2)实例属性 和 类属性
内建函数 dir()可以显示类属性,也可以打印所有实例属性
从实例中访问类属性须谨慎:
任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。如果类属性中存在同名的属性,则会覆盖对类属性的引用。所以,给一个与类属性同名的实例属性赋值,我们会有效地“隐藏”类属性,但一旦我们删除了这个实例属性,类属性又重见天日。
类属性的持久性
3 绑定与方法调用
方法是类属性而非实例属性;
方法只有在其所属的类拥有实例时,才能被调用。当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的;
任何一个方法定义中的第一个参数都是变量 self,它表示调用此方法的实例对象
(1)调用绑定方法
即正常的先构建出一个类的实例,然后通过该实例调用该类的方法(因为此时方法已经与实例绑定了!)
(2)调用非绑定方法
调用非绑定方法不常用。调用一个还没有任何实例的类中的方法的主要场景是:你在派生一个子类,而且你要覆盖父类的方法,这时你需要调用那个父类中想要覆盖掉的构造方法:
class EmplAddrBookEntry(AddrBookEntry):
'Employee Address Book Entry class' # 员工地址记录条目
def __init__(self, nm, ph, em):
AddrBookEntry.__init__( self, nm, ph) #此即调用非绑定方法。当还没有实例且需要调用一个非绑定方法的时候必须传递self 参数
self.empid = id
self.email = em
(3)静态方法 和 类方法
创建方法1:使用staticmethod()和 classmethod()内建函数
class TestStaticMethod:
def foo():
print 'calling static method foo()'
foo = staticmethod(foo)
class TestClassMethod:
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
foo = classmethod(foo)
创建方法2:使用函数修饰符
class TestStaticMethod:
@staticmethod
def foo():
print 'calling static method foo()'
class TestClassMethod:
@classmethod
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
三、组合
在代码中利用类的两种方法:组合 + 继承
组合是一种has-a关系
四、继承、子类和派生
1 __base__类属性
它是一个包含其父类的集合的元组,
2 通过继承覆盖方法
举例说明:
class P(object):
def foo(self):
print 'Hi, I am P-foo()'
class C(P):
def foo(self):
print 'Hi, I am C-foo()'
>>> c = C()
>>> c.foo()
Hi, I am C-foo() 尽管C继承了P的foo()方法,但因为C定义了自已的 foo()方法,所以P中的foo()方法被覆盖
如何调用那个被我覆盖的基类方法呢:
方法一:
>>> P.foo( c ) 这是在调用非绑定方法
Hi, I am P-foo()
方法二:
class C(P):
def foo(self):
P.foo( self ) 在子类的重写方法里显式地调用基类方法(也是在调用非绑定方法)
print 'Hi, I am C-foo()'
方法三:
class C(P):
def foo(self):
super( C, self ).foo()
print 'Hi, I am C-foo()'
3 从标准类型派生
举例1:继承不可变标准类型的例子
假定你想在金融应用中,应用一个处理浮点数的子类。每次你得到一个贷币值(浮点数给出的),你都需要通过四舍五入,变为带两位小数位的数值。
class RoundFloat(float): 继承float
def __new__(cls, val):
return float.__new__(cls, round(val, 2))
或写成:
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls, round(val, 2))
举例2:继承可变标准类型的例子
该例子创建一个新的字典类型,其keys()方法会自动排序结果
class SortedKeyDict(dict):
def keys(self):
return sorted( super( SortedKeyDict, self ).keys())
4 多重继承
复杂,暂且没看!
五、类、实例和其他对象的内建函数
issubclass(sub, sup)
isinstance(obj, class)
hasattr(myInst, 'foo')
getattr(myInst, 'foo')
setattr(myInst, 'bar', 'my attr')
delattr(myInst, 'foo')
dir( obj )
super( type[, obj] ) 给出type,super()会返回此type的父类。若你希望父类被绑定,你可以传入obj参数(obj可以是type类型的一个实例;obj也可以是一个类型,但应当是type的一个子类)
vars(obj) 返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值
六、用特殊方法定制类
可以重写python中的一些特殊方法以定制类,从而可以实现两大功能:
模拟标准类型
重载操作符
用来定制类的特殊方法列举如下:
基本定制型:
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.__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) 获取属性;内建 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) True 除;/操作符
C.__*floordiv__(self, obj) 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) 在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等)
序列类型
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) 测试序列成员;内建 in 关键字
C.__*add__(self,obj) 串连;+操作符
C.__*mul__(self,obj) 重复;*操作符
C.__iter__(self) 创建迭代类;内建 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) 给定键如果不存在字典中,则提供一个默认值
1 简单定制举例
目标:自定义一个类来保存浮点数,且自动实现四舍五入并保留两位小数
class RoundFloatManual(object):
def __init__(self, val):
assert isinstance(val, float), \
"Value must be a float!"
self.value = round( val, 2 )
此时若如下用会出现这样的效果:
>>> rfm = RoundFloatManual(42)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "roundFloat2.py", line 5, in __init__
assert isinstance(val, float), \ AssertionError: Value must be a float!
>>> rfm = RoundFloatManual(4.2)
>>> rfm 本来此处按常理理解应该打印出浮点数最好,但实际并没有,因为我们没有对类进行定制
<roundFloat2.RoundFloatManual object at 0x63030>
>>> print rfm 本来此处按常理理解应该打印出浮点数最好,但实际并没有,因为我们没有对类进行定制
<roundFloat2.RoundFloatManual object at 0x63030>
解决办法:好的办法是,去实现__str__()和__repr__()二者之一,或者两者都实现
现添重载__str__()和__repr__()方法,以覆盖默认的行为:
def __str__(self):
return '%.2f' % self.value
__repr__ = __str_ #由于本例中两个函数的代码可以完全一样,所以可以仅让__repr__()作为__str__()的一个别名
这样打印操作就正常了:
>>> rfm = RoundFloatManual(5.5964)
>>> rfm 此处显示正常是由于重写了__repr__()方法的效果
5.60
>>> print rfm 此处显示正常是由于重写了__str__()方法的效果
5.60
2 数值定制举例
目标:创建一个Time60时间类
class Time60(object):
def __init__(self, hr, min): # constructor 构造器
self.hr = hr # assign hours 给小时赋值
self.min = min # assign minutes 给分赋值
显示:
def __str__(self): #重写方法
return '%d:%d' % (self.hr, self.min)
__repr__ = __str__ #重写方法
加法:
def __add__(self, other): #重写方法
return self.__class__(self.hr + other.hr, self.min + other.min)
原位加法:用来支持像 mon += tue 这样的操作符
def __iadd__(self, other): #重写方法
self.hr += other.hr
self.min += other.min
return self
3 迭代器定制举例
class AnyIter(object):
def __init__(self, data, safe=False):
self.safe = safe
self.iter = iter(data)
def __iter__(self):
return self
def next(self, howmany=1):
retval = []
for eachItem in range(howmany):
try:
retval.append( self.iter.next() )
except StopIteration:
if self.safe:
break
else:
raise
return retval
使用:
>>> a = AnyIter(range(10))
>>> i = iter(a)
>>> for j in range(1,5):
>>> ... print j, ':', i.next(j)
1 : [0]
2 : [1, 2]
3 : [3, 4, 5]
4 : [6, 7, 8, 9]
七、私有化
python的属性默认是public
双下划线:
由双下划线开始的属性在运行时被“混淆”,所以不允许直接访问
单下划线:(验证有问题?)
简单的模块级私有化只需要在属性名前使用一个单下划线字符。这就防止模块的属性用“from mymodule import*”来加载。这是严格基于作用域的,所以这同样适合于函数。
八、授权与包装
九、新式类的高级特性