第十二篇:python中的面向对象
编程范式
编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式或风格,对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。
面向过程编程
面向过程编程依赖procedures(过程),一个procedure包含一组要被进行计算的步骤,面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
类与对象
类和对象是面向对象编程的2个非常重要的概念。
类:类是对象的类型,具有相同属性和行为事物的统称。类是抽象的,在使用的时候通常会找到这个类的一个具体存在。
对象:万物皆对象,对象拥有自己的特征和行为。是类中一个具体存在的实例。
类与对象的关系
类是对象的类型,对象是类的实例。类是抽象的概念,而对象是一个你能够摸得着,看得到的实体。二者相辅相成,谁也离不开谁。
现实世界中的类:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在。
在程序中:务必保证先定义类,后产生对象
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
注意: 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类...... ,有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类。
创建类
语法:class 类名:
定义变量
定义函数
#python中的类 class Fruit(): generate = '植物上长的果子' def grow_up(self): print('果子在成长中') #python中的对象 f1 = Fruit() #调用类或称为实例化,得到对象。即f1是Fruit类实例化的对象
操作属性
python中一切都是对象,类本身也是对象,有自己的属性,可以通过 类名.__dict__查找。类中可以有任意python代码,这些代码在类定义阶段便会执行,因而会产生新的名称空间,用来存放类的变量名与函数名,而这些在类中定义的名字,都是类的属性,都可以通过类名访问,类名加点(.)是访问属性的语法。所有类的属性都存储到类的__dict__字典里,也可以通过字典的形式访问。
#python中的类 class Fruit(): generate = '植物上长的果子' def grow_up(self): print('果子在成长中') #查看类的所有属性 print(Fruit.__dict__) # . :专门用来访问属性,本质操作的就是__dict__ print(Fruit.generate) #等于 Fruit.__dict__['generate'] Fruit.generate = 18 #等于 Fruit.__dict__['generate'] = 18 Fruit.x = 2 #等于 Fruit.__dict__['generate'] = 2 del Fruit.generate #等于 del Fruit.__dict__['generate']
类属性有两种:数据属性(变量名)和函数属性(函数名)
数据属性:即类变量,类变量在整个实例化的对象中是公用的,在对象与类没有同名的属性前提下,实例化的对象可以访问类变量,但不能修改类变量。类变量定义在类中且在函数体之外。如果需要用在类的方法中,使用 类名.类属性.。
#python中的类 class Fruit(): generate = '植物上长的果子' def grow_up(self): print(Fruit.generate) #访问类变量 print('果子在成长中') # #python中的对象 f1 = Fruit() #f1是Fruit类实例化的对象 f2 = Fruit() #f1是Fruit类实例化的对象 print(f1.generate) print((f2.generate)) ##类的数据属性是所有对象共享的,id都一样 print(id(f1.generate)) print(id(f1.generate)) #对象不能修改类变量 f1.generate = 29 #实质是对象f1为自己添加generate属性 f2.generate = 28 print('类属性 ======>',Fruit.generate) print('对象f1的属性 ======>',f1.generate) #若对象与类有同名属性,对象访问该属性,则是访问自己的属性。 print('对象f2的属性 ======>',f2.generate) print(id(Fruit.generate)) print(id(f1.generate)) print(id(f2.generate))
函数属性:类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,也叫普通方法。外部用实例调用,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数,其实普通方法主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理),绑定到对象的方法的这种自动传值的特征,决定了在类中定义的普通方法都要默认写一个参数self,并且是普通方法的第一个位置参数。self可以是任意名字,但是约定俗成地写出self。
#python中的类 class Fruit(): generate = '植物上长的果子' def grow_up(self): print('果子在成长中') #类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样,id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准。 f1 = Fruit() f2 = Fruit() f3 = Fruit() print(Fruit.grow_up) print(f1.grow_up) print(f2.grow_up) print(f3.grow_up)
python的自动传值:对象调用类的普通方法,则python自动把对象本身传给该方法的第一个位置参数。
#python中的类 class Fruit(): generate = '植物上长的果子' def grow_up(self): # print('果子在成长中') print('参数self是:{}'.format(self)) f1 = Fruit() f2 = Fruit() print('对象f1:%s'%f1) f1.grow_up() #等同于 Fruit.grow_up(f1) print('对象f2:%s'%f2) f2.grow_up() #等同于 Fruit.grow_up(f2)
#python为类内置的特殊属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
实例属性:同类的对象,除了相似的属性之外还各种不同的属性,定义在类的普通函数中,并且以普通函数的第一个位置参数做前缀的变量,叫做实例属性。可以为类实例化的对象修改或删除对象属性的,上面说到普通函数的第一个位置参数传入的其实就是一个对象,当该函数被调用时,函数里的代码被执行。实例属性就会被操作到对应的对象属性中,可以通过对象的__dict__属性查看对象的属性。通过对象属性可以体现对象与对象在类中的独特之处。
#python中的类 class Fruit(): generate = '植物上长的果子' def grow_up(self): self.name = '苹果' print('果子在成长中') f1 = Fruit() f2 = Fruit() #查看对象的属性 print('对象f1的属性字典:%s'%f1.__dict__) #返回{}空字典,因为对象f1被实例化后没有添加任何属性。 print('对象f2的属性字典:%s'%f2.__dict__,end='\n'*2) #返回{}空字典,因为对象f2被实例化后没有添加任何属性。 f1.name = '香蕉' #给对象添加属性name,等于 f1.__dict__['name'] = '香蕉' f2.colour = '红色' print('对象f1的属性字典:%s'%f1.__dict__) print('对象f2的属性字典:%s'%f2.__dict__,end='\n'*2) f2.colour = '蓝色' #修改对象属性 print('对象f2的属性字典:%s'%f2.__dict__) #调用有定义实例属性的函数 f1.grow_up() #等同于 Fruit.grow_up(f1) f2.grow_up() #等同于 Fruit.grow_up(f2) print('对象f1的属性字典:%s'%f1.__dict__) print('对象f2的属性字典:%s'%f2.__dict__,end='\n'*2)
__init__()构造方法
__init__()是一个特殊的方法属于类的专有方法,被称为类的构造函数或初始化方法,方法的前面和后面都有两个下划线。这是为了避免Python默认方法和普通方法发生名称的冲突。每当创建类的实例化对象的时候,__init__()方法都会默认被运行。作用就是初始化已实例化后的对象,即该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值。实例属性一般在该函数中定义。
class Fruit(): generate = '植物上长的果子' def __init__(self,name,colour): self.name = name #定义实例属性,通过传参给实例化的对象初始化属性。 self.colour = colour print('__init__函数被调用') print('{}是{}的'.format(self.name,self.colour)) def grow_up(self): self.name = '苹果' print('果子在成长中') f1 = Fruit('苹果','红色') #创建一个新对象后,调用Fruit类的__init__()方法,把对象和参数传给函数,然后返回这个对象给变量f1引用。 f2 = Fruit('香蕉','黄色') #查看对象的属性 print('对象f1的属性字典:%s'%f1.__dict__) print('对象f2的属性字典:%s'%f2.__dict__)
#方式一 class Fruit(): generate = '植物上长的果子' def grow_up(self): self.name = '苹果' print('果子在成长中') # 实例化出两个个空对象 obj1=Fruit() obj2=Fruit() # 为对象定制自己独有的特征 obj1.name='苹果' obj1.colour='红色' obj2.name='香蕉' obj2.colour='黄色' print(obj1.__dict__) print(obj2.__dict__) print(Fruit.__dict__) #方式二 class Fruit(): generate = '植物上长的果子' def grow_up(self): self.name = '苹果' print('果子在成长中') # 实例化出两个空对象 obj1=Fruit() obj2=Fruit() # 为对象定制自己独有的特征 def initialize(obj, x, y): obj.name = x obj.age = y initialize(obj1,'苹果','红色') initialize(obj2,'香蕉','黄色') print(obj1.__dict__) print(obj2.__dict__) print(Fruit.__dict__) #方式三 class Fruit(): generate = '植物上长的果子' def initialize(self,name,colour): self.name = name #通过传参给实例化的对象初始化属性。 self.colour = colour print('__init__函数被调用') print('{}是{}的'.format(self.name,self.colour)) def grow_up(self): self.name = '苹果' print('果子在成长中') obj1=Fruit() obj1.initialize('苹果','红色') #Fruit.initialize(obj1,'苹果','红色') obj2=Fruit() obj2.initialize('香蕉','黄色') #Fruit.initialize(obj2,'香蕉','黄色') print(obj1.__dict__) print(obj2.__dict__) print(Fruit.__dict__) # 方式四 class Fruit(): generate = '植物上长的果子' def __init__(self,name,colour): self.name = name #通过传参给实例化的对象初始化属性。 self.colour = colour print('__init__函数被调用') print('{}是{}的'.format(self.name,self.colour)) def grow_up(self): self.name = '苹果' print('果子在成长中') obj1=Fruit('苹果','红色') #Fruit.__init__(obj1,'苹果','红色') obj2=Fruit('香蕉','黄色') #Fruit.__init__(obj2,'香蕉','黄色') print(obj1.__dict__) print(obj2.__dict__) print(Fruit.__dict__)
from pymysql import connect #1、在没有学习类这个概念时,数据与功能是分离的 def exc1(host,port,db,charset,sql): conn=connect(host,port,db,charset) xxx = conn.execute(sql) return xxx def exc2(host,port,db,charset,sql): conn=connect(host,port,db,charset) xxx = conn.call_proc(sql) return xxx #每次调用都需要重复传入一堆参数 exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;') exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字') #2、我们能想到的解决方法是,把这些变量都定义成全局变量 HOST='127.0.0.1' PORT=3306 DB='db1' CHARSET='utf-8' def exc1(host,port,db,charset,sql): conn=connect(host,port,db,charset) xxx = conn.execute(sql) return xxx def exc2(host,port,db,charset,sql): conn=connect(host,port,db,charset) xxx = conn.call_proc(sql) return xxx exc1(HOST,PORT,DB,CHARSET,'select * from tb1;') exc2(HOST,PORT,DB,CHARSET,'存储过程的名字') #3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用,然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset def exc1(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.execute(sql) return res def exc2(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.call_proc(sql) return res obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字') #改进 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset self.conn=connect(self.host,self.port,self.db,self.charset) def exc1(self,sql): return self.conn.execute(sql) def exc2(self,sql): return self.conn.call_proc(sql) obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字')
静态属性、类方法、静态方法
静态属性
定义在类中,通过装饰器 @property装饰的方法,并且只有一个参数。只允许实例对象调用。
class Fruit(): generate = '植物上长的果子' def __init__(self,name,colour): self.name = name #通过传参给实例化的对象初始化属性。 self.colour = colour print('__init__函数被调用') print('{}是{}的'.format(self.name,self.colour)) @property def grow_up(self): print('果子在成长中') return self.name,Fruit.generate f1 = Fruit('苹果','红色') print(f1.grow_up)
类方法
定义在类中,通过装饰器 @classmethod装饰的方法,此方法是专门绑定给类调用的,当然实例对象也可以调用,但方法里不能访问实例属性。同时必须有一个位置参数cls,并且用此来调用类属性:cls.类属性名。与对象调用普通方法一样,调用此函数时,python会把调用者本身传给第一个位置参数。
class Fruit(): generate = '植物上长的果子' def __init__(self,name,colour): self.name = name #通过传参给实例化的对象初始化属性。 self.colour = colour @classmethod def grow_up(cls): print('grow_up被执行') cls.weight = 39 #设置类属性,即使是实例对象调用,也是设置为实例对象的类属性,非实例对象的独特属性。 return cls.generate f1 = Fruit('苹果','红色') print(f1.grow_up()) print(f1.__dict__) print(Fruit.__dict__,end='\n'*2) print(Fruit.grow_up()) print(f1.__dict__) print(Fruit.__dict__)
静态方法
定义在类中,通过装饰器 @staticmethod装饰的方法,不能访问实例属性 ,与类相关但是不依赖类与实例对象,可以通过类或者实例来调用。python对此方法没有默认自动传值,不与类或对象绑定,不需必须有参数,可以是无参函数。
class Fruit(): generate = '植物上长的果子' def __init__(self,name,colour): self.name = name #通过传参给实例化的对象初始化属性。 self.colour = colour @staticmethod def grow_up(cls): print('grow_up被执行') Fruit.generate += cls return Fruit.generate print(Fruit.__dict__,end='\n'*2) f1 = Fruit('苹果','红色') print(f1.grow_up('39')) print(f1.__dict__) print(Fruit.__dict__,end='\n'*2) print(Fruit.grow_up(' 39')) print(f1.__dict__) print(Fruit.__dict__)
面向对象的三大特性
继承/派生/继承结构
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。子类会“”遗传”父类的属性,从而解决代码重用问题。如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时。我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.。
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(SubClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass #查看父类,__bases__则是查看所有继承的父类,注意,不包括父类的父类。 print(ParentClass1.__bases__) print(ParentClass2.__bases__) print(SubClass1.__bases__) print(SubClass2.__bases__) #判断继承关系 print(issubclass(SubClass2,ParentClass2)) print(issubclass(SubClass2,ParentClass1)) #不管几代的继承,无论是直接继承还是间接继承,只要存在继承关系,就返回True。
重用性与继承
继承可以实现代码重用,我们把类与类之间相似或者说比较像的部分抽取成类,这个过程称之为抽象,所以在做继承之前,必须先抽象。抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
#不用不使用继承写的猫类和狗类 class Dog: def __init__(self,name): self.name = name def cry(self): print('汪汪叫') def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat: def __init__(self,name): self.name = name def cry(self): print('喵喵叫') def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) ''' 从上面代码可以看出猫类和狗类有大量相同的内容,而我们却分别在猫和狗的类中编写了两次。 如果使用 继承 的思想,把相似功能的代码都写到另一类中,猫和狗都是动物,所以我们可以写 一个动物类,然后继承,如下实现: ''' class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def cry(self): print('汪汪叫')
属性查找顺序(继承顺序)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,通过 类名.__mro__ 可以查看MRO列表。为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
在多继承中,如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先。新式类使用深度优先的查找顺序,经典类(没有继承object的新建类)使用广度优先。python3中统一都是新式类,pyhon2中才分新式类与经典类。
class A(object): a = 0 def test(self): print('from A') class B(A): a = 1 def test(self): print('from B') class C(A): a = 2 def test(self): print('from C') class D(B): a = 3 def test(self): print('from D') class E(C): a = 5 def test(self): print('from E') class F(D,E): a = 8 # def __init__(self,a): # self.a = a def test(self): print('from F') pass print(F.__mro__) f=F() # f=F(9) print(f.a) f.test()
子类中调用父类的方法
方法一:指名道姓,即父类名.父类方法(),这种类调用的方法,函数有多少个参数,就需要传入多少给参数。
方法二:使用函数super(),该函数会自动传入实例对象。
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。即使没有直接继承关系,super仍然会按照mro继续往后查找,只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
class A: def __init__(self,x,y): self.A_x = x self.A_y = y class B: def __init__(self,x,y): self.B_x = x self.B_y = y class C(A,B): def __init__(self,x,y): # B.__init__(self,x,y) #当继承的类中有同样属性,指名道姓可指定哪个类的属性, super().__init__(x,y) #按照mro列表顺序查找,一旦找到就会停止查找 # super(__class__,self).__init__(x,y) # super(C,self).__init__(x,y) c = C('abc',9) print(c.__dict__)
派生与方法重写
当然子类也可以添加自己新的属性或者在自己这里重新定义继承的属性(不会影响到父类),函数属性被重新定义则叫方法重写。需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值。
class A: def __init__(self,x,y): self.A_x = x self.A_y = y class B(A): def __init__(self,x,y,i,j): #方法重写 self.B_x = x #派生属性 self.B_y = y #派生属性 A.__init__(self,i,j) #调用父类方法 b = B('a','b','c','d') print(b.__dict__,end='\n'*2) b.__init__(1,2,8,9) print(b.__dict__,end='\n'*2) #实例对象调用该类函数属性 A.__init__(b,'ab','cd') #调用父类方法 print(b.__dict__)
接口与归一化设计
接口是一种将对象标记为符合给定API或约定的机制,定义了一群类共同的函数,但未实现,而是让子类去实现接口中的函数。这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,比如,有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样,不过,在python中没有提供实现接口的功能,如果非要去模仿接口的概念,可以借助第三方模块:http://pypi.python.org/pypi/zope.interface。
基于继承我们可以实现模仿接口,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(Interface): #磁盘,具体实现read和write def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法')
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类。
抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计 。如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
在python中实现抽象类
#一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法')
继承结构:表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是一类事务的多种形态,比如水有固态、液态、气态,Python的序列类型有多种形态:字符串,列表,元组,字典。
多态性指在不考虑实例类型的情况下使用实例的方法。在Python中很多地方都可以体现多态的特性,比如内置函数len(object),len函数不仅可以计算字符串的长度,还可以计算列表、元组等对象中的数据个数,这里在运行时通过参数类型确定其具体的计算过程。多态性的好处是:增加了程序的灵活性(以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal))、增加了程序额可扩展性。
'''不同类型上的实例对象体现的多态性''' class Water: def __init__(self,bottle): self.bottle = bottle def temperature(self,centigrade): if centigrade <= 0: print('{}是液态水,常温下大于零'.format(self.bottle)) else: print('{}的温度是:{} 摄氏度'.format(self.bottle,centigrade)) class Ice: def __init__(self,bottle): self.bottle = bottle def temperature(self,centigrade): if centigrade >= 0: print('{}是固态冰,常温下小于零摄氏度'.format(self.bottle)) else: print('{}的温度是:{} 摄氏度'.format(self.bottle,centigrade)) b1 = Water('一号瓶子') b2 = Ice('二号瓶子') #不同类的实例对象调用相同的方法 b1.temperature(30) b2.temperature(30) b2.temperature(-20) '''继承上体现的多态性''' class Water: def __init__(self,bottle): self.bottle = bottle def temperature(self,centigrade): if centigrade <= 0: print('{}是液态水,常温下大于零'.format(self.bottle)) else: print('{}的温度是:{} 摄氏度'.format(self.bottle,centigrade)) class Ice(Water): def __init__(self,bottle): super().__init__(bottle) def temperature(self,centigrade): if centigrade >= 0: print('{}是固态冰,常温下小于零摄氏度'.format(self.bottle)) else: print('{}的温度是:{} 摄氏度'.format(self.bottle,centigrade)) b1 = Water('一号瓶子') b2 = Ice('二号瓶子') #子类与父类的实例对象调用相同的方法 b1.temperature(30) b2.temperature(30) b2.temperature(-20)
封装
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。
'''定义类本身就是一种封装方式''' class A: N=0 def __init__(self): self.X=10 def foo(self): print('from A') a = A() print(a.N) a.foo()
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
这种自动变形的特点:
- 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
- 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
- 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
也可以通过j@property特性来实现函数隐藏。property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。将一个类的函数定义成此特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') @property def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. return 8
这种变形需要注意的问题是:
这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') @property def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. return 8 print(A._A__N) a = A() print(a._A__X) print(a.__dict__) print(A.__dict__) '''变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形''' a.__Y = 20 A.__Z = 90 print(a.__dict__) print(a.__Y) print(A.__dict__) print(A.__Z)
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的。
class A: def __fa(self): # 在定义时就变形为_A__fa print('from A') def test(self): self.__fa() # 只会与自己所在的类为准,即调用_A__fa class B(A): def __fa(self): print('from B') b = B() b.test()
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__。
合成
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
class Equip: # 武器装备类 def fire(self): print('release Fire skill') class Riven: # 英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 camp = 'Noxus' def __init__(self, nickname): self.nickname = nickname self.equip = Equip() # 用Equip类产生一个装备,赋值给实例的equip属性 r1 = Riven('关雨竹') r1.equip.fire() # 可以使用组合的类产生的对象所持有的方法
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同:
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。
自省(反射)
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
python提供了四个可以实现自省的函数。适用于类和对象(一切皆对象,类本身也是一个对象)。
class Foo(object): X = 30 def __init__(self): self.y = 20 def func(self): return 'func' @staticmethod def bar(): return 'bar' f = Foo() #检测是否含有某属性 print(hasattr(f,'y')) print(hasattr(Foo,'X')) #获取属性,若属性不存在则报错。可设置第三个参数为报错时返回的信息。 print(getattr(f,'y')) print(getattr(f,'x','属性不存在')) print(getattr(Foo,'X')) #设置属性 setattr(f,'x',15) #添加 setattr(Foo,'X',90) #修改 print(f.__dict__) #删除属性 delattr(f,'y') delattr(Foo,'X') print(f.__dict__)
python的魔术方法参考链接:https://blog.csdn.net/NightCharm/article/details/79357559