面向对象
一 面向对象VS面向过程
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。
面向对象的程序设计的
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
类:具有相同特征的一类事物(人、狗、老虎)
对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
实例化:类——>对象的过程(这在生活中表现的不明显,我们在后面再慢慢解释)
二 初识类和对象
python中一切皆为对象,类型的本质就是类。(类也是对象)
>>> dict #类型dict就是类dict
<class 'dict'>
>>> d=dict(name='eva') #实例化
>>> d.pop('name') #向d发一条消息,执行d的方法pop
'eva'
从上面的例子来看,字典就是一类数据结构,我一说字典你就知道是那个用{}表示,里面由k-v键值对的东西,它还具有一些增删改查的方法。但是我一说字典你能知道字典里具体存了哪些内容么?不能,所以我们说对于一个类来说,它具有相同的特征属性和方法。
而具体的{'name':'eva'}这个字典,它是一个字典,可以使用字典的所有方法,并且里面有了具体的值,它就是字典的一个对象。对象就是已经实实在在存在的某一个具体的个体。
在python中,用变量表示特征,用函数表示技能,因而具有相同特征和技能的一类事物就是‘类’,对象是则是这一类事物中具体的一个。
在现实世界中:先有对象,再有类。
对象:李军
特征:
国籍=中国
性别=男
技能:
学习
吃饭
在程序世界中:先有类,再有对象。
类:中国
成员:
对象1=李军
对象2=张三
对象3=万五
细说__init__方法
#方式一、为对象初始化自己独有的特征
class People:
country='China'
x=1
def run(self):
print('----->', self)
# 实例化出三个空对象
obj1=People()
obj2=People()
obj3=People()
# 为对象定制自己独有的特征
obj1.name='egon'
obj1.age=18
obj1.sex='male'
obj2.name='lxx'
obj2.age=38
obj2.sex='female'
obj3.name='alex'
obj3.age=38
obj3.sex='female'
# print(obj1.__dict__)
# print(obj2.__dict__)
# print(obj3.__dict__)
# print(People.__dict__)
#方式二、为对象初始化自己独有的特征
class People:
country='China'
x=1
def run(self):
print('----->', self)
# 实例化出三个空对象
obj1=People()
obj2=People()
obj3=People()
# 为对象定制自己独有的特征
def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
obj.name = x
obj.age = y
obj.sex = z
chu_shi_hua(obj1,'egon',18,'male')
chu_shi_hua(obj2,'lxx',38,'female')
chu_shi_hua(obj3,'alex',38,'female')
#方式三、为对象初始化自己独有的特征
class People:
country='China'
x=1
def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
obj.name = x
obj.age = y
obj.sex = z
def run(self):
print('----->', self)
obj1=People()
# print(People.chu_shi_hua)
People.chu_shi_hua(obj1,'egon',18,'male')
obj2=People()
People.chu_shi_hua(obj2,'lxx',38,'female')
obj3=People()
People.chu_shi_hua(obj3,'alex',38,'female')
# 方式四、为对象初始化自己独有的特征
class People:
country='China'
x=1
def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
obj.name = x
obj.age = y
obj.sex = z
def run(self):
print('----->', self)
obj1=People('egon',18,'male') #People.__init__(obj1,'egon',18,'male')
obj2=People('lxx',38,'female') #People.__init__(obj2,'lxx',38,'female')
obj3=People('alex',38,'female') #People.__init__(obj3,'alex',38,'female')
# __init__方法
# 强调:
# 1、该方法内可以有任意的python代码
# 2、一定不能有返回值
class People:
country='China'
x=1
def __init__(obj, name, age, sex): #obj=obj1,x='egon',y=18,z='male'
# if type(name) is not str:
# raise TypeError('名字必须是字符串类型')
obj.name = name
obj.age = age
obj.sex = sex
def run(self):
print('----->', self)
# obj1=People('egon',18,'male')
obj1=People(3537,18,'male')
# print(obj1.run)
# obj1.run() #People.run(obj1)
# print(People.run)
!!!__init__方法之为对象定制自己独有的特征
三 属性查找
类有两种属性:数据属性和函数属性
1.类的数据属性是所有对象共享的
2.类的函数属性是绑定给对象用的
#类的数据属性是所有对象共享的,id都一样
print(id(OldboyStudent.school))
print(id(s1.school))
print(id(s2.school))
print(id(s3.school))
'''
4377347328
4377347328
4377347328
4377347328
'''
#类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样
#ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准
print(OldboyStudent.learn)
print(s1.learn)
print(s2.learn)
print(s3.learn)
'''
<function OldboyStudent.learn at 0x1021329d8>
<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>>
<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>>
<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>>
'''
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
四 绑定到对象的方法的特殊之处
#改写
class OldboyStudent:
school='oldboy'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def learn(self):
print('%s is learning' %self.name) #新增self.name
def eat(self):
print('%s is eating' %self.name)
def sleep(self):
print('%s is sleeping' %self.name)
s1=OldboyStudent('李坦克','男',18)
s2=OldboyStudent('王大炮','女',38)
s3=OldboyStudent('牛榴弹','男',78)
类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
OldboyStudent.learn(s1) #李坦克 is learning
OldboyStudent.learn(s2) #王大炮 is learning
OldboyStudent.learn(s3) #牛榴弹 is learning
类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
s1.learn() #等同于OldboyStudent.learn(s1)
s2.learn() #等同于OldboyStudent.learn(s2)
s3.learn() #等同于OldboyStudent.learn(s3)
注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
类即类型
提示:python的class术语与c++有一定区别,与 Modula-3更像。
python中一切皆为对象,且python3中类与类型是一个概念,类型就是类
#类型dict就是类dict
>>> list
<class 'list'>
#实例化的到3个对象l1,l2,l3
>>> l1=list()
>>> l2=list()
>>> l3=list()
#三个对象都有绑定方法append,是相同的功能,但内存地址不同
>>> l1.append
<built-in method append of list object at 0x10b482b48>
>>> l2.append
<built-in method append of list object at 0x10b482b88>
>>> l3.append
<built-in method append of list object at 0x10b482bc8>
#操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3
>>> l1.append(3)
>>> l1
[3]
>>> l2
[]
>>> l3
[]
#调用类list.append(l3,111)等同于l3.append(111)
>>> list.append(l3,111) #l3.append(111)
>>> l3
[111]
五 对象之间的交互
class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;
camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia;
def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...;
self.nickname=nickname #为自己的盖伦起个别名;
self.aggressivity=aggressivity #英雄都有自己的攻击力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻击技能,enemy是敌人;
enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
我们可以仿照garen类再创建一个Riven类
class Riven:
camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus;
def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54;
self.nickname=nickname #为自己的锐雯起个别名;
self.aggressivity=aggressivity #英雄都有自己的攻击力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻击技能,enemy是敌人;
enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
实例出俩英雄
>>> g1=Garen('草丛伦')
>>> r1=Riven('锐雯雯')
交互:锐雯雯攻击草丛伦,反之一样
>>> g1.life_value
455
>>> r1.attack(g1)
>>> g1.life_value
401
六 继承与派生
1 初识继承
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生或子类。
Python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
3.在python3中,无论是否声明继承object,都默认继承object,即python3中所有类均为新式类
#关于新式类与经典类的区别,我们稍后讨论
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
2 继承与抽象(先抽象再继承)
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
3 继承与重用性
例子
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('汪汪叫')
# ######### 执行 #########
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
'''
类猫与狗都要相同的吃喝拉撒的功能,而这些功能都是他们相同的功能,于是抽象给了动物这个类,
并且各自继承了这个,就避免在自己的类中重复定义,这种在类中调用父类的功能或数据的方式叫做重用
'''
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
4 派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
print('from riven')
def fly(self): #在自己这里定义新的
print('%s is flying' %self.nickname)
5 组合与重用性
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
>>> 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() #可以使用组合的类产生的对象所持有的方法
release Fire skill
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
class People:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
class Course:
def __init__(self,name,period,price):
self.name=name
self.period=period
self.price=price
def tell_info(self):
print('<%s %s %s>' %(self.name,self.period,self.price))
class Teacher(People):
def __init__(self,name,age,sex,job_title):
People.__init__(self,name,age,sex)
self.job_title=job_title
self.course=[]
self.students=[]
class Student(People):
def __init__(self,name,age,sex):
People.__init__(self,name,age,sex)
self.course=[]
egon=Teacher('egon',18,'male','沙河霸道金牌讲师')
s1=Student('牛榴弹',18,'female')
python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)
#为老师egon和学生s1添加课程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
#为老师egon添加学生s1
egon.students.append(s1)
#使用
for obj in egon.course:
obj.tell_info()
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
6 继承实现的原理(菱形问题)
(1) 继承顺序
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
(2) 继承原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
7 子类调用父类的方法
方法一:
指名道姓,即父类名.父类方法()
#_*_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()
了解部分:
即使没有直接继承关系,super仍然会按照mro继续往后查找
#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A,B):
pass
c=C()
c.test() #打印结果:from B
print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
七 多态与多态性
1 多态
多态指的是一类事物有多重形态
动物有多重形态:人,狗,猪
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
注意:
多态是指一类事物有多重形态,
而抽象则是将则多重形态抽取相同的部分放在父类中。
2 多态性
(1)什么是多态动态绑定
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
(2) 为什么要用多态性
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
(3) 鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
def read(self):
pass
def write(self):
pass
class DiskFile:
def read(self):
pass
def write(self):
pass
例2:其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下
#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))
#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
len(s)
len(l)
len(t)
八 封装
1 什么是封装
封装即是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据行为(或功能)相结合,形成一个有机的整体。
2 如何隐藏
通过self.__属性名把类的数据属性设置成私有的,其隐藏是对外不对内,就是在外部绑定对象后,对象无法以对象.__属性名的方式访问到该属性,但是在类的内部则是可以访问。但在语法上外部可以通过对象._类名.__属性名的方式访问到该名字。
注意:
1.在定义类或者初始化对象时,在属性前加__,就会将该属性隐藏起来
但该隐藏起始只是一种变形_类名__属性名,并没有真正隐藏起来
2.该变形操作是在类定义阶段扫描语法发生的变形,类定义之后添加的__开头的属性不会发生变形
3.该隐藏式对外不对内的
4.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有
#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
#类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:
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的形式访问到.
#A._A__N是可以访问到的,
#这种,在外部是无法通过__x这个名字访问到。
3 封装不是单纯意义的隐藏
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能再外部使用,但外部想要用类隐藏的接口,就需要我们在类中为其开辟接口,让外部间接用到我们隐藏的属性名。
意义:
1.将数据隐藏起来不是目的,隐藏起来然后对外提供操作该数据的接口,
然后我们在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher:
def __init__(self,name,age):
# self.__name=name
# self.__age=age
self.set_info(name,age)
def tell_info(self):
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
t=Teacher('egon',18)
t.tell_info()
t.set_info('egon',19)
t.tell_info()
2.隔离复杂度,在编程语言中,对外提供接口,可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a=ATM()
a.withdraw()
4 特性property
property是一种特殊的属性,访问它会执行一段功能(函数)然后返回值。
为何要用:
在外部即是用调用数据的方法调用这个功能,并直接得到返回值,这样对象根本
无法察觉到自己是执行了一个函数计算出来的,这种特性的使用方式遵循了统一访问的原则。
import math
class Circle:
def __init__(self,radius): #圆的半径radius
self.radius=radius
@property
def area(self):
return math.pi * self.radius**2 #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
九 绑定与非绑定方法
1 类中定义的函数分成两大类
(1):绑定方法(绑定给谁,谁来调用就自动将它本身当做第一个参数传入):
1.绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
2.绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值一说)
(2):非绑定方法:用staticmethod装饰器装饰的方法
1.不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说,类与对象都可以调用
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
2 绑定方法
绑定给对象的方法(略)
绑定给类的方法(classmethod)
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向对象编程\test1\db'
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
3 非绑定方法
在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id(): #就是一个普通工具
m=hashlib.md5(str(time.time()).encode('utf-8'))
return m.hexdigest()
print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数