Python面向对象的学习
面向对象(Object Oriented Programming)编程主要有三大特性:继承、封装、多态。那就从这三个方面来学习一下,也会具体说说Python类的一些特性,如新式类和旧式类,__slots__,super等等。
1、封装
封装是最好理解的,它是将内容都封装到类中,隐藏了具体的细节,然后通过属性方式来访问。
如:
class A:
name = 's'
def __init__(self,a,b):
self.a = a
self.b = b
上面一个简单的例子,将a,b两个值封装到A的属性a,b中。我们访问则是通过A().a,A().b访问。
属性也分类属性和实例属性,name是类属性,a,b是实例属性。当我们定义类属性后,就不要在实例属性中定义了,否则会覆盖掉类属性。
现在具体说一下属性。
类属性一般都保存在类的__dict__中,而实力属性一般都保存在实例的__dict__中。我们可以把相应的值打印出来:
class A(object):
age = 43
def __init__(self):
print 'A'
self.name = 'a'
self.age = 23
def test(self):
print 'test'
a = A()
print a.__dict__
print a.age
print a.__class__.__dict__
输出:
A
{'age': 23, 'name': 'a'}
23
{'__module__': '__main__', 'age': 43, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, '__init__': <function __init__ at 0x000000000268FEB8>}
具体分析一下上面的例子:
- age分别有一个类属性和实例属性,分别保存在实例和类的__dict__中。
- 当我们print age的时候,是打印实例属性,而不是类属性,这和点号运算搜索过程有关。
- 实例本身的dict,a.__dict__;
- 类的dict,a.__class__.__dict__;
- 调用定义的__getattr__或者__getattribute__方法(新式类,具体在我的Python内置特性一文中有详细介绍)
- 子类;
如果经过上述几个步骤依然找不到属性,就会抛出ArrtibuteError。
那现在问题来了,实例的方法到底是什么访问过程呢,即a.test()?其实a.test()其实就相当于:a.__class__.__dict__['test'].__get__(a,A)。这是一个bound method.,就是绑定了实例的方法。这要说几个概念,unbound method,function,bound method。
当一个函数(function)定义在了class语句的块中(或者由 type
来创建的), 它会转成一个 unbound method
, 当我们通过一个类的实例来 访问这个函数的时候,它就转成了 bound method
, bound method
会自动把这个实例作为函数的地一个参数。
所以, bound method
就是绑定了一个实例的方法, 否则叫做 unbound method
.它们都是方法(method), 是出现在 class
中的函数。
3.类的__dict__并不是真正意义的dict,而是一个dictproxy。它是只读的,比如,你想做:
a.__class__.__dict__['s'] = 1
TypeError: 'dictproxy' object does not support item assignment
说到属性访问,要提一下Python的描述符:property,经过property装饰的方法就可以通过属性的方式访问。比如我定义了一个
class A(object):
@property
def test(self,x):
pass
就可以通过A().test访问,此外,我们也可以通过property,.setter以及property.getter等方法来实现一些定制的读写属性。比如:
class A(object):
@property
def score(self):
return self._score
@score.setter
def score(self,value):
if value>100:
raise ValueError('int must be lower than 100')
self._score = value
a = A()
a.score = 101
系统会抛出ValueError的异常。
学过Java的都知道Java规定了public,private等属性,但在Python中是没有的,Python也可以分出不同类别的属性,比如可以用双下划线来表示,但这只是规定了一种规则,却不是强制限制,一切凭自觉。嗯,Python开发者都是素质高的一类人,哈哈。
不过,你非要写成这种形式,可以写一些代码实现:
class PrivateExc(Exception):
pass
class A(object):
def __setattr__(self, key, value):
if key not in self.privates:
raise PrivateExc('{0} not in {1} '.format(key,self))
else:
self.__dict__[key] = value
class B(A):
privates = ['age']
def __init__(self,age):
self.age = age
def __getattr__(self, item):
return item
b = B(12)
b.age = 30
print b.age
b.name = 'h'
30
Traceback (most recent call last):
File "D:/Study/Programming/Python/myworks/weiborobot/__init__.py", line 32, in <module>
b.name = 'h'
File "D:/Study/Programming/Python/myworks/weiborobot/__init__.py", line 15, in __setattr__
raise PrivateExc('{0} not in {1} '.format(key,self))
__main__.PrivateExc: name not in <__main__.B object at 0x00000000025A7780>
更多关于描述符的可参考我的另外一篇文章:Python描述符
2、继承
在Python中,继承分为单继承和多重继承(Java只有单继承),单继承比较好理解,单纯地继承父类的属性和方法。 这里只说一下多重继承。有的时候我们定义一个子类,并不是希望定义非常复杂的父类进行单继承来实现多种功能,Python支持 多继承,用的最多的就是Minxin,这在Django中也经常用到。即我希望可以实现多种功能。比如一个视图类CreateView,我既希望继承父类View的方法,又想继承表单的一些功能,那:
class BaseCreateView(ModelFormMixin, ProcessFormView):
pass
多重继承主要考虑的问题是假如一个子类的父类有两个,如果两个父类都有相同的方法,那到底该继承哪一个父类的方法呢?这就涉及到了方法的查找顺序。
对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 当你使用 super()
函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super()
并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次 。 这个MRO列表就是一个简单的所有基类的线性顺序表。为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
不同形式的类的多重继承方法也不同,类主要分为经典类和新式类。新式类指的是继承自object的类,其他的就叫做经典类。 经典类使用的继承查找顺序是深度优先,新式类使用的继承方法是C3广度优先。 深度优先和广度优先是算法中两个比较重要的搜索算法。
现在看经典类的例子:
class A:
def output(self):
print 'A'
class B(A):
pass
class C(A):
def output(self):
print 'C'
class D(B,C):
pass
d = D()
d.output()
输出是A
这是因为它的查找顺序是D->B->A->C,完全符合深度优先。画一下树形图
A
B C
D
那么其实到这儿是有问题的,对于子类D,我希望它继承C的output,但是它最后是继承了A的output方法。对于这种问题,就产生了新式类。
新式类的写法主要是要继承object.
class A(object):
def output(self):
print 'A'
class B(A):
pass
class C(A):
def output(self):
print 'C'
class D(B,C):
pass
d = D()
d.output()
输出C
这是因为它使用C3广度优先算法,它的查找顺序是D->B->C->A。它完美解决了经典类遇到的问题。
既然说到了新式类,那新式类除了继承带来的优势,还有什么新的特性呢?
- MRO 该表
- __slots__等新属性;
- super;
- descriptor;
(1)MRO表
上面已经说过了,即关于多重继承时候,搜索过程。
(2)__slots__等新属性;
在新式类中加入了很多的新特性。你定义完一个新式类后,打印属性,会有如下这些:
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'output']
__getattribute__就是新式类的一种用法,要比__getattr__更灵活。
当我们在新式类中定义了__slots__后, Python就会为实例使用一种更加紧凑的内部表示。 实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。比如,假设你不使用slots直接存储一个Date实例, 在64位的Python上面要占用428字节,而如果使用了slots,内存占用下降到156字节。 如果程序中需要同时创建大量的日期实例,那么这个就能极大的减小内存使用量了。
目前存在的最大误区是__slots__可作为封装工具防止添加新属性,虽然它的确可实现这个目的,但这不是__slots的初衷,它就是用来优化数据结构的,不过我们还是拿个例子来显示说一下用__slots__后会产生哪些效果:
class Haibo(object):
__slots__ = ('name','height')
d = D()
d.age = 23
print d.age
我们对Haibo这个类,只限定它拥有name和height两个属性,当我们试着要给实例添加age属性时,就会抛出ArributeError异常.这隐士地表明了,定义__slots__之后,就不具有__dict__特性了。
__slots__很有用,它能够为程序节省很多的内存消耗,尤其是对于那些只需要简单数据结构的类。但也不要轻易就用它,它会使类失去很多功能,比如多重继承。
(3)super
super在我们编写代码时,做类继承时用得非常多,可以说遍地都是,这也证明了它给带来的优势。
一般当写子类时,我们要重构父类的方法,并也希望继承父类的方法,就要用super,用了super,减少了代码的编写量,当然你也可以说,不用super,直接显示引用父类的方法,这也可以,但还是遇到问题,加入你再两个子类中显示调用父类A的__init__。那D中会调用两次A的__init__,而用super就不会出现那种情况。
此外,super最大最大的优势是在多重方面的应用。它为实例提供了不同的mro策略。
简单例子:
class A(object):
def __init__(self):
print 'A'
class B(A):
def __init__(self):
print 'B'
super(B,self).__init__()
b = B()
super其实是一个类,不是函数。 super 包含了两个非常重要的信息: 一个 MRO 以及 MRO 中的一个类。
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
两个参数 cls 和 inst 分别做了两件事:
1. inst 负责生成 MRO 的 list
2. 通过 cls 定位当前 MRO 中的 index, 并返回 mro[index + 1]
当我们使用了super类的时候,就会向mro表中写入。还是拿个例子:
class A(object):
def __init__(self):
print 'A'
class B(A):
def __init__(self):
print 'B'
super(B,self).__init__()
class C(A):
def __init__(self):
print 'C'
super(C,self).__init__()
class D(B,C):
pass
d = D()
print d.__class__.__mro__
输出:
B
C
A
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
搜索原则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
(4)描述符
我自己测试的时候发现旧式类也能用啊, 把我整懵逼了。
3、多态
多态即多种形态,在编译阶段无法确定类型,在运行时确定状态。Python不像Java那样需要声明变量类型,需要在运行时确定变量的类型,其实这就是多态的体现。
多态本质上是对不同的对象可以实现相同的操作,通常是为了接口重用,即不同的类实现相同的方法。(运算符重载其实也是一种多态。)
上面的例子中其实就是一种多态,子类进行了运算符重载,不同的子类实现了相同的方法output,但输出的结果是不一样的,这就是多态。 Python中引用了“鸭子类型”,它就是一种多态,什么是鸭子类型? “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。 比如一些像文件的对象,如StringIO,可以像文件一样操作,使用文件的方法。
例子:
class Duck:
def quack(self):
print 'gua gua'
class Bird:
def quack(self):
print 'bird is like bird'
def speak(obj):
obj.quack()
obj1 = Duck()
obj2 = Bird()
speak(obj1)
speak(obj2)
speak函数输入了两个不同的参数,他们输出的结果是不同的,这就是多态。
类的方法:
1、普通方法
普通方法很好理解,大多数都是普通方法,即第一个参数是类本身,即self.
2、类方法
classmethod装饰的方法,传入的第一个参数是cls
3、静态方法
staticmethod装饰的方法,不需要传入参数。
class Animal():
def mth1(self):
print 'normal method'
@classmethod
def mth2(cls):
print 'class method'
@staticmethod
def mth3():
print 'static method'
a = Animal()
a.mth1()
Animal.mth2()
Animal.mth3()
a.mth2()
a.mth3()
微信分享/微信扫码阅读