python学习【七】 面向对象编程
讲面向对象之前,这里先对前面使用到的编程方式做一个简单的总结:
- 面向过程:这是最简单也最容易所使用的一种编程方式,只需要根据业务逻辑从上到下将代码垒起来就可以了,代码的执行过程也是从上到下执行。
- 函数式编程:将实现某个业务逻辑的代码封装到一个函数中,当需要的时候只需要通过函数名调用即可。
面向对象
面向对象编程(Object Oriented Programming),简称OOP,它是一种程序设计思想。面向对象其实就是对类(Class)和对象(object)的使用。
- 类:用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。就像人们对事物的分类一样。
- 对象:对象是对类的实例化。
面向对象具有三大特性:封装、多态、继承。
下面用一个简单的实例对面向对象的特性进行介绍:
封装
假设现在要定一个人的类,我们就会想到人具有的相同属性(如:姓名、年龄、体重、身高等)和行为(如:说话、吃饭、走路等)。通过程序我们可以封装成下面的形式:
#!/usr/bin/env python # -*- coding:utf-8 -*- #通过class关键字创建类Pepole class Pepole:
info='test' #构造方法,又称为初始化方法。对类进行实例化时被调用。 def __init__(self,name,age): self.name=name self.age=age def say(self): print 'hi,my name is %s' % self.name def getAge(self): print 'My age is %d'%self.age
上面就是对人的定义,这里需要了解以下几点:
- 对于初始化的属性(name、age)必须赋值给self,否则在类中的其他地方将无法使用。
- self:形式参数,表示类实例化后的对象。每个方法的第一个形参必须是self。
- 对于info这样的变量要想在类中的其他地方使用,则需用通过类名进行调用。如:Pepole.info。
下面对Pepole进行实例化:
p1=Pepole('andy',18) p1.say() p1.getAge()
p1为Pepole实例化后产生的对象。我们可以通过p1来调用类中的方法。输出结果如下:
hi,my name is andy My age is 18
self的深入讲解
上面已经说了self是类实例化后的对象。可能你会存在一些困惑,这里只需要一个简单的例子就能解开你的困惑。代码如下:
class Pepole: def say(self): print self p1=Pepole() print 'p1:',p1 p1.say() p2=Pepole() print 'p2:',p2 p2.say()
执行结果:
p1: <__main__.Pepole object at 0x016F6CF0> <__main__.Pepole object at 0x016F6CF0> p2: <__main__.Pepole object at 0x017401B0> <__main__.Pepole object at 0x017401B0>
通过上面的结果,就可以清楚的看出self等价于类实例化后的对象。
总结如下图所示:
继承
继承是子类自动共享父类中属性和方法的机制,这是类之间的一种关系。和现实生活中儿子可以继承父亲的一些特点一样。
例如:现在有一个人叫andy除了上面定义的共同特征外,他还会唱歌、跳舞。为了方便我们只需要创建一个有唱歌和跳舞的类就可以了,共同的部分可以从Pepole类中继承。因此,这里的Pepole就被称为“基类”,Andy被称为“派生类”。
代码如下:
class Andy(Pepole): def dancing(self): print 'I will dance' def singing(self): print 'I will sing' p3=Andy('andy',18) p3.say() p3.getAge() p3.dancing() p3.singing()
结果如下:
hi,my name is andy My age is 18 I will dance I will sing
在python中,一个类可以继承多个类。由于这一特性,派生类寻找基类的方式又分为两种:深度优先和广度优先。
- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类与新式类的区分如下:
类成员
类的成员可以分为三大类:字段、方法和属性。
字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
- 普通字段属于对象
- 静态字段属于类
普通字段与静态字段的区别如下图所示:
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段和值,那么就使用静态字段
方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法(@classmethod):由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法(@staticmethod):由类调用;无默认参数;
属性(@property)
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种,使用方式与使用字段相似。
对于属性,有以下两个知识点:
1、属性的基本使用
class Foo: def func(self): pass # 定义属性 @property def prop(self): pass # ############### 调用 ############### foo_obj = Foo() foo_obj.func() foo_obj.prop #调用属性
属性的定义和调用要注意一下几点:
- 定义时,在普通方法的基础上添加 @property 装饰器;
- 定义时,属性仅有一个self参数
- 调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
2、属性的两种定义方式
装饰器 即:在方法上应用装饰器
静态字段 即:在类中定义值为property对象的静态字段
类成员修饰符
类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问,我们平时使用的就是公有成员
- 私有成员,只有在类的内部才能方法,私有成员命名时,以两个下划线开头。如:__name
私有成员和公有成员的访问限制不同:
静态字段
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
- 私有静态字段:仅类内部可以访问;
普通字段
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
- 私有普通字段:仅类内部可以访问;
注:如果想要强制访问私有字段,可以通过 【对象._类名__私有字段明 】访问(如:obj._C__foo),不建议强制访问私有成员。
类相关的内置方法
一、isinstance(obj,cls)
用于检查obj是否是类cls的实例化对象。
在介绍类的内置方法之前,先回忆一下之前我们是怎么查看一个变量是什么类型的。
例如:
>>> n1 = 10 >>> n2 = "str" >>> type(n1) <type 'int'> >>> type(n2) <type'str'>
因为一切事物皆是对象,因此这里n1和n2也是int和str的对象。上面用type()查看了变量的类型,但是却无法使用这个结果做其他操作(判断n1是否是int类型)。而isinstance()方法就是来解决这个问题的。
需求:有列表l1=['aa',11,22,'bb','cc','dd'],把l1中是str类型的存到str_list=[],把int类型的存入到int_list=[]。
l1=['aa',11,22,'bb','cc','dd']
str_list=[]
int_list=[]
for temp in l1:
if isinstance(temp,str):
str_list.append(temp)
if isinstance(temp,int):
int_list.append(temp)
print str_list
print int_list
结果为:
str_list: ['aa', 'bb', 'cc', 'dd'] int_list: [11, 22]
isinstance(obj,cls)不仅可以检查obj是否是cls的对象,还可以检查obj是否是cls的基类的对象。
例如:
class A: pass class B(A): pass b=B() print isinstance(b,B) print isinstance(b,A)
结果为:
True True
总结:当类B继承类A时,对象b即是类B的实例也是类A的实例
二、issubclass(args1,args2)
用于判断args1是否是args2的派生类。
例如:
class A: pass class B(A): pass print issubclass(B,A)
结果为:
True