python面向对象编程
阅读目录
一.面向对象基础知识
二.类属性与实例属性
三.静态属性、静态方法、类方法
四.组合
五.继承与派生
六.多态与多态性
七.封装
八.python中关于OOP的常用术语
一.面向对象基础知识
属性
#属性 # 类是用来描述一类事物,类的对象 # 1.数据属性:变量 # 2.函数属性:函数,在面向对象里通常称为方法
#注意:类和对象均用点来访问自己的属性 class Person:
#数据属性 government='dang'
#函数属性
def eat(): print("eat") def sleep(): print("sleep") print(Person.government) print(Person.eat()) print(Person.sleep())
查看类属性
#我们定义的类的属性到底存在哪里了?下面有两种方法查看 # dir(类名):查出的是一个名字列表 # 类名.__dict__:查出的是一个字典,Key为属性名,value为属性值 print(dir(Person)) print(Person.__dict__) #现在我们知道属性都存在类的属性字典中,所以我们现在调用类的属性就可以这样调用 print(Person.__dict__['government']) print(Person.__dict__['eat']()) print(Person.__dict__['sleep']())
特殊类属性
print(Person.__name__)#类的名字(字符串) print(Person.__doc__)#类的文档,使用''''''注释的内容 print(Person.__base__)#类的第一个父类 print(Person.__bases__)#类的所有父类构成的元组 print(Person.__dict__)#类的属性 print(Person.__module__)#显示类在哪个模块 print(Person.__class__)#显示类的类型
实例化
# 实例化 class Person: government='dang' def __init__(self,name,age,gender): self.d={ "name":name, "age":age, "gender":gender } def eat(): print("eat") def sleep(): print("sleep") p1=Person('liyi',20,1) # p1=Person.__init__(p1,'liyi',20,1) print(p1.d) print(p1.__dict__) #1.每一个对象初始化都需要__init__()函数定制对象的属性,这是一个类的内置函数,第一个参数一定要为self #实例化过程: # 执行Person('liyi',20,1) # 然后到类里去找 __init__()函数,来初始化对象,将数据进行封装然后返回实例字典,这样我们才能通过__dict__()函数查看属性,这里的属性只有实例属性,没有类属性 # 我们传入三个参数,但是 __init__()函数有四个形参,因为第一个self参数对应的为p1这个对象.
为什么实例能访问到类数据属性
#我们使用__dict__()函数查看这个实例的属性,发现里面只有初始化的数据属性,为什么他能访问类属性呢? #__init__()函数也是有作用域的,这样说其实就是实例也有自己的'作用域',先在自己的实例属性里找,然后到类的属性找,都没有才会报错
实例怎么调用类函数属性
class Person: government='dang' def __init__(self,name,age,gender): self.d={ "name":name, "age":age, "gender":gender } def eat(self): print("%s eat") def sleep(self): print("sleep") p1=Person('liyi',20,1) p1.eat() #我们知道可以通过类名.属性来调用类的函数属性,但是这和我们实例出来的对象没有半毛钱关系,我们想要对象也能调用类的函数属性。 #如果大家注意的话,会看到eat和sleep函数的参数里多了self,self代表自己。 #p1.eat(),如果你这样写,p1这个实例对象就会被自动传参,所以类里面的eat方法想要self来接收这个实例对象。 #总结:实例化的时候,实例化对象会被自动传入,实例调用类的函数函数属性时,也要被自动传入,所以__init__函数和类的函数属性第一个参数来接收实例本身,所以我们明白了实例要调用函数属性,那个函数属性一定要有参数self来接收实例。
二、类属性与实例属性
类属性与实例属性的增删改查
class Person: Country="China" def __init__(self,name,age,gender): self.d={ "name":name, "age":age, "gender":gender } def eat(self): print("%s eat" %self.d['name']) def sleep(self): print("%s sleep" %self.d['name']) #查看类属性 print(Person.Country) #修改类属性 Person.Country="CHINA" print(Person.Country) #删除类属性 del Person.Country #增加类属性,对于函数属性,只需要定义一个函数,也一样就行 Person.location='Asia' print(Person.__dict__)
类属性与实例属性的独立性
class Person: Country="China" def __init__(self,name,age,gender): self.d={ "name":name, "age":age, "gender":gender } def eat(self): print("%s eat" %self.d['name']) def sleep(self): print("%s sleep" %self.d['name']) p1=Person('liyi',20,1) print(p1.Country) p1.Country="CHINA" print(Person.Country) print(p1.Country) #类的属性是类的属性,实例是实例的属性,互相独立
三.静态属性、静态方法、类方法
静态属性
class Room: def __init__(self,name,owner,width,height): self.name=name self.owner=owner self.width=width self.height=height @property def mianji(self): return self.width*self.height r1=Room("502","liyi",20,25) print(r1.mianji) print(r1.name) #如果我们不加@property,当我们实例化r1,调用r1.mianji() #加上@property后,将函数属性给你做了封装,r1.mianji调用就ok了 #给我们最大的感受就是,我们可以像调用数据属性一样调用如r1.name # 总结:把函数封装成数据属性的形式,实例调用的时候看不到内部的逻辑 #特点:需要与实例绑定(也就是函数参数第一个为self) # 能访问实例的数据属性 # 能访问类的数据属性与函数属性
类方法
class Person: government='dang' def eat(): print("eat") def sleep(): print("sleep") print(Person.government) print(Person.eat()) print(Person.sleep()) #上面这里类调用自己的数据属性是完全没有问题的,调用自己的函数属性也没有问题 #下面的类调用自己的函数属性就不行了,因为调用函数属性必须传一个参数,前面我们知道了这个self必须和实例绑在一起(实例调用的时候自动传入) #所以我们似乎必须得实例出一个对象,让对象来调用函数属性才行 #如果我们现在就不想通过实例来调用,就是想让类能调用自己的函数属性怎么做 class Person: government='dang' @classmethod def classMethod(self): print("我是类方法") def eat(self): print("eat") def sleep(self): print("sleep") print(Person.government) print(Person.classMethod()) p1=Person() p1.eat() p1.sleep() #注意:classMethod(slef)是有参数的,但是类调用的时候,没有传入参数,怎么做到的,其实这里和实例调用函数属性一样,类调用自己的类方法也是把类自动传入。 #和实例没有需求,只是类级别的操作,因为它传入的是类本身,所以只能访问类自己的属性,不能访问实例的属性 #总结:第一个参数为self,但是由类调用,所以传入的是类本身,只能访问类的数据属性和函数属性,不能访问实例的属性
静态方法
class Person: government='dang' @staticmethod def wash_body(): print() def eat(self): print("eat") def sleep(self): print("sleep") #staticmethod静态方法只是名义上归属类管理,不能使用类变量和实例变量,是类的工具包 #静态方法不与类绑定,也不与实例绑定 #这样做,这个方法与类和实例都没有关系,就相当于类的工具包 #总结:不会传入类或者实例,所以类与实例都可以调用,不能访问类属性,也不能访问实例属性
四.组合
组合用来做类与类之间的关联
class Hand: pass class Foot: pass class Trunk: pass class Head: pass class Person: def __init__(self,id_num,name): self.name=name self.id_num=id_num self.hand=Hand() self.foot=Foot() self.trunk=Trunk() self.head=Head() p1=Person(12443,"liyi") print(p1.__dict__)
高级的组合
class School: def __init__(self,name,addr): self.name=name self.addr=addr class Course: def __init__(self,name,price,period,school): self.name=name self.price=price self.period=period self.school=school s1=School('教育机构','北京') s2=School('教育机构','上海') #我们要让课程与学校之间建立联系,所以我们直接把学校对象s1传进去 c1=Course('linux',88,500,s1) #我们查看c1的属性字典,school属性为对象类型 print(c1.__dict__) #我们这样就建立了学校和课程之间的联系,如下就可以调用关联类的属性 print(c1.school.name) print(c1.school.addr) #这样做有点不好,我们这里是手动将s1传入,我们的需求是只要创建课程对象,就会自动与学校对象关联上。
组合的使用示例
class School: def __init__(self,name,addr): self.name=name self.addr=addr class Course: def __init__(self,name,price,period,school): self.name=name self.price=price self.period=period self.school=school s1=School('教育机构','北京') s2=School('教育机构','上海') s3=School('教育机构','北京') #我们要让课程与学校之间建立联系,所以我们直接把学校对象s1传进去 while 1: menu={'1':s1, '2':s2, '3':s3, 'quit':" " } choice=input("选择学校>>:") if choice==menu['quit']:break school_obj=menu[choice] name=input("课程名:") price=input("课程费用:") period=input("课程时间:") new_course=Course(name,price,period,school_obj) print("课程:%s 课程费用:%s 课程时间:%s 课程学校:%s 课程学校地址:%s" %(new_course.name,new_course.price,new_course.period,new_course.school.name,new_course.school.addr))
五.继承与派生
什么是类的继承?
# 类的继承跟现实生活中的父、子、孙子、重孙子、继承关系一样,父类又称为基类 # python中类的继承分为:单继承和多继承 class ParentClass1: pass class ParentClass2: pass class SubClass(ParentClass1):#单继承 pass class SubClass2(ParentClass1,ParentClass2):#多继承 pass
查看继承关系
print(SubClass.__bases__) print(SubClass2.__bases__)
(<class '__main__.ParentClass1'>,) (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
子继承到底继承了父类的什么属性?
class ParentClass: money=100000 def __init__(self,name): self.name=name def hit_son(self): print("%s 正在打儿子" %self.name) class SubClass(ParentClass):#单继承 money = 100 s1=SubClass('liyi') print(SubClass.money)#子类访问父类数据属性 print(s1.money)#实例访问自己的数据属性 print(ParentClass.money)#父类访问自己的数据属性 s1.hit_son()#自动调用父类的函数属性 print(ParentClass.__dict__) print(SubClass.__dict__) #我们要先理解子类实例化,先到子类找__init__()进行实例化,如果没有才会去父类查看__init__()函数进行初始化 #子类会继承父类的所有属性 # 注意:子类自定义的属性如果跟父类重名了,那就覆盖了父类属性这句话是错的,找的时候先在自己这找,然后到父类中找
什么时候用继承?
# 1.当类之间有显著不同,并且较小的类是较大类所需要的组件时,用组合比较好 # 2.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
class Cat: def eat(self): pass def sleep(self): pass class Dog: def eat(self): pass def sleep(self): pass
class Anmial: def __init__(self,name): self.name=name def eat(self): pass def sleep(self): pass class Cat(Anmial): def miaomiaojiao(self):#自定义的函数属性,这里子类继承基类,扩展自己的方法 pass def eat(self):#在基类的基础上改变方法,为重写代码 print("%s 正在吃东西"%self.name) class Dog(Anmial): def wangwangjiao(self):#自定义的函数属性,这里子类继承基类,在此基础上扩展了自己的方法 pass
继承的两种含义
#含义一:继承基类的方法,并且做出自己的改变或者扩展(代码重用) #含义二:声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法 #实际中,继承的第一种含义意义不大,甚至常常有害,因为它使得子类与基类出现强耦合 # 继承的第二种含义非常重要,它叫“接口继承” # 接口继承实质上做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用这无需关心具体细节,可一视同仁的处理特定接口的所有对象---这在程序设计上叫做归一化
接口继承
接口:子类继承基类,实现基类的方法。子类实现基类的方法不是强制性的。
接口是提取了一些函数共同的功能,可以把接口当做一个函数的集合。
在子类中去实现接口中定义的的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如: 我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
再比如: 我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样。
class Tools: def add(self): pass def delete(self): pass def change(self): pass def select(self): pass class tool1(Tools): def add(self): print("实现add方法")
抽象类
什么是抽象类?
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。
为什么要有抽象类?
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的。
注: 抽象方法是基类中定义的方法,但却没有任何实现。
python中抽象类的实现
#!/usr/bin/env python #-*- coding:utf-8 -*- __author__ = 'Shuke' #一切皆文件 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('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() # 实例化 yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type) ''' 执行结果: 文本数据的读取方法 硬盘数据的读取方法 进程数据的读取方法 file file file '''
抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
接口可以不用在子类中实现基类的方法
抽象类在子类中必须实现基类的方法
继承实现的原理(继承顺序)
继承顺序 1. python中的类可以继承多个类,java和c#中则只能继承一个类。 2. python中的类如果继承了多个类,那么其寻找方法的方式有两种,分别是深度优先和广度优先。
注:
- 当类是经典类时,多继承的情况下,则会按照深度优先的顺序进行查找
- 当类是新式类时,多继承的情况下,则会按照广度优先的顺序进行查找
经典类和新式类,从字面上面可以看出一个新一个旧,新的必然包含了更多的功能,也是之后推荐的用法,从写法上区分,如果当前类或者父类继承了object类,那么当前类便是新式类,否则便是经典类。
pyhon2中才分新式类与经典类。python3中统一都是新式类。
class C1: # C1是经典类 pass class C2(C1): # C2是经典类 pass
class N1(object): # N1是新式类 pass class N2(N1): # N2是新式类 pass
广度优先的继承顺序
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
继承原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__ [, , , , , , ]
python会按照列表里面类的排列顺序进行查找
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1. 子类会先于父类被检查
2. 多个父类会根据它们在列表中的顺序被检查
3. 如果对下一个类存在两个合法的选择,选择第一个父类
子类中调用父类方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方式一: 父类名.父类方法()
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): Vehicle.__init__(self,name,speed,load,power) # 调用:父类名.父类方法() self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) Vehicle.run(self) line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run()
方式二: super()
class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run() class Mobike(Vehicle):#摩拜单车 pass line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run() 方法2
不用super()函数引发的惨案
#!/usr/bin/env python #-*- coding:utf-8 -*- #每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') A.__init__(self) class C(A): def __init__(self): print('C的构造方法') A.__init__(self) class D(B,C): def __init__(self): print('D的构造方法') B.__init__(self) C.__init__(self) pass f1=D() print(D.__mro__) #python2中没有这个属性 ''' 执行结果: D的构造方法 B的构造方法 A的构造方法 C的构造方法 A的构造方法 (, ''' 示例, , , )
当使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重新定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次
注: 使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表.
#!/usr/bin/env python #-*- coding:utf-8 -*- #每个类中都继承了且重写了父类的方法 class A: 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): def __init__(self): print('D的构造方法') super(D,self).__init__() f1=D() print(D.__mro__) # python2中没有这个属性 ''' 执行结果: D的构造方法 B的构造方法 C的构造方法 A的构造方法 (, ''' 示例, , , )
六、多态与多态性
什么是多态性?
多态与多态性是两种不同的概念。
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同功能的函数。
在面向对象方法中一般是这样表述多态性: 向不同的对象发送同一条消息(obj.func():是调用了obj的func方法,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同。
多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性: 如下所示
>>> def func(obj): ... print(obj.__len__()) ... >>> func('Hello') >>> func([0,1,2,3]) >>> func((7,8,9)) 示例1
#!/usr/bin/python # -*- coding:utf-8 -*- class Animal: def talk(self): print('Is talking') class People(Animal): def talk(self): print('say hello...') class Pig(Animal): def talk(self): print('say hengheng...') class Dog(Animal): def talk(self): print('say wangwang...') class Cat(Animal): def talk(self): print('say miaomiao...') def func(obj): # 参数obj就是对态性的体现 obj.talk() # 执行同一个方法 peo1=People() # 产生一个人的对象 pig1=Pig() # 产生一个猪的对象 dog1=Dog() # 产生一个狗的对象 cat1=Cat() # 产生一个猫的对象 func(peo1) func(pig1) func(dog1) func(cat1) ''' 执行结果: say hello... say hengheng... say wangwang... say miaomiao... ''' 示例2
#!/usr/bin/env python #-*- coding:utf-8 -*- import abc class File(metaclass=abc.ABCMeta): @abc.abstractmethod def click(self): pass class Text(File): def click(self): print('open file') class ExeFile(File): def click(self): print('execute file') def func(file): # 参数file就是对态性的体现 file.click() t1=Text() # 实例化一个文本文件的对象 e1=ExeFile() # 实例化一个可执行文件的对象 func(t1) func(e1) ''' 执行结果: open file execute file ''' 示例3
综上所述: 多态性是一个接口(函数func),多种实现如obj.talk()
多态性的好处?(优点)
优点:
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
>>> class Cat(Animal): #属于动物的另外一种形态:猫 ... def talk(self): ... print('say miao') ... >>> def func(animal): #对于使用者来说,自己的代码根本无需改动 ... animal.talk() ... >>> cat1=Cat() #实例出一只猫 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 say miao ''' 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) '''
总结:多态一定是在继承的基础上实现,多态的概念指出了对象如何通过共同的属性和动作来操作及访问,而不需要考虑他们具体的类
多态表明了动态(又名:运行时)绑定的存在,允许重载及运行时类型确定和验证
七.封装
class People: star="1" _star="2" __star="3" def __init__(self,id,name,age,gender): self.__id=id self.__name=name self.__age=age self.__gender=gender def get_shuxing(self): print(self.__star) print(self.__id) print(self.__name) print(self.__age) print(self.__gender) p1=People(22,'liyi',20,1) print(p1.star) print(p1._star) # print(p1.__star) # print(People.__star) print(People.__dict__) print(People._People__star) print(p1.get_shuxing()) #所谓封装 #装很好理解,把属性,不管数据属性,函数属性用类装起来 #封怎么理解,封,这里的封装是使用户只管调用,不用管内部逻辑的实现,当然也不能访问内部属性 # 内部和外部:内部指类,外部指在类外使用 #_ __ #一般我们在类里的属性,外部也是可以访问的,我们怎么做才能让属性不能被外部访问 #在python中,如果给属性的名字前加上_代表这个属性是不能被外部访问的,但是这是一种约定,是不强制的,所以用户还是能访问 #但是如果你在属性的前面加上__,那么就不能被访问,但是如果我们使用类.__dict__查看发现,其实又有问题了,这里的不能访问只不过是python帮你把这个属性给改名字了而已,你通过People._People__star还是能访问 #所以我们到头来还是不能真正的实现封这个效果 #真正意义上的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供了一个访问接口给外部接口使用(这才是真正的封装,具体) #把用户用不着的属性给私有化,不让用户访问. # 把用户能用得到的属性也给私有化,但是留出接口给外部访问 #所以我们在编程中要深思,不要随便把一个属性变成私有属性 #没必要隐藏(私有化的属性,尽量就不要去私有化) # 总结:装是定义属性,封是让属性不能被外部访问,只能出固定的内部接口来统一访问
为什么要封装?
封装数据的主要原因是:保护隐私(把不想别人知道的东西封装起来)
封装方法的主要原因是:隔离复杂度(比如:电视机,我们看见的就是一个黑匣子,其实里面有很多电器元件,对于用户来说,我们不需要清楚里面都有些元件,电视机把那些电器元件封装在黑匣子里,提供给用户的只是几个按钮接口,通过按钮就能实现对电视机的操作。)
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
封装分为两个层面
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。
注: 对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
第二个层面的封装: 类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到.
这种自动变形的特点:
1. 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2. 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3. 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注: 对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
>>> a=Animal() >>> a._Animal__name # 在类外部一种特殊的访问私有属性的方式 >>> a._Animal__age >>>Animal._Animal__sex
2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
class A: __N = 10 def __init__(self): self.__X = 10 a= A() print(a.__dict__) a.__Y=5 print(a.__dict__) ''' 执行结果: {'_A__X': 10} {'__Y': 5, '_A__X': 10} # 定义后的赋值操作,__Y,没有变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
>>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B 正常情况
#把fa定义成私有的,即__fa >>> 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() from A 私有
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点。
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶。
八.python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注: 封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明。
(注: 对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气。
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样。
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__。
面向对象的软件工程包括下面几个部: 8.1 面向对象分析(object oriented analysis ,OOA) 软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。 建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。 8.2 面向对象设计(object oriented design,OOD) 根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。 首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。 在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述。 8.3 面向对象编程(object oriented programming,OOP) 根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python,也可以是别的编程语言。 8.4 面向对象测试(object oriented test,OOT) 在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。 面向对象的测试是用面向对象的方法进行测试,以类作为测试的基本单元。 8.5 面向对象维护(object oriendted soft maintenance,OOSM) 正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。 由于使用了面向对象的方法开发程序,使用程序的维护比较容易。 因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。 在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。 现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。 在一个大型软件开发过程中,OOP只是很小的一个部分。 对于全栈开发的你来说,这五个阶段都有了,对于简单的问题,不必严格按照这个5个阶段进行,往往由程序设计者按照面向对象的方法进行程序设计,包括类的设计和程序的设计。
参考:https://www.cnblogs.com/aslongas/p/6991966.html#_label12
参考:http://www.cnblogs.com/linhaifeng/articles/6182264.html#_label17