第一:程序设计思想与发展历程(了解)
1940年以前:面向机器编程
最早的是采用机器语言编程,也就是直接使用二进制码来表示机器能够识别的指令和数据。
优点:机器语言由机器直接执行,速度快
缺点:写起来非常困难,并且不容易修改
汇编语言:用助记符号代替机器指令的操作码,用地址符号或者标号代替指令或操作数的地址
优点:比机器语言的二进制码编写方便些
缺点:汇编语言本质上还是一种面向机器的语言,编写困难,易出错
脱离机器后:面向过程编程
面向过程的结构化程序设计强调功能的抽象和程序的模块化, 它将解决问题的过程看作是一个处理过程,如c语言
优点:对底层的硬件,内存,cpu等操作比较方便
缺点:不易扩展,写代码,调试比较麻烦
第一次软件危机:结构化程序设计
采取“自顶向下、逐步细化、模块化”的指导思想。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软 件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度
1960年爆发,背景:由于软件质量低下,项目无法如期完成、项目严重超支,因为软件而导致的重大事故经常发生,主要体现在复杂性上
1968年第一个结构化程序语言Pascal诞生
1970年成为软件开发的主流
第二次软件危机:面向对象程序设计
根本原因:软件生产力远远跟不上硬件和业务的发展。
主要体现在‘可扩展性’与‘可维护性’上
早在1967年的Simula语言就提出面向对象,第二次危机促进了面向对象的发展
1980s年代,得益于C++,以及后来java,C#把面向对象推向了高峰,现在已经成为主流开发思想
第二:什么是面向对象程序设计
首先在这说一下什么是面向过程设计:
面向过程的程序设计的核心是过程,过程即解决问题的步骤。面向过程可理解成一条流水线
优点是:极大的降低了程序的复杂度
缺点是:一套流水线或者流程就是用来解决一个问题,如果是生产面包的流水线不可能生产包子,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象设计:
面向对象的程序设计的核心是对象,对象可理解成特征,属性等
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是神也无法预测最终结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
第三:类和对象
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
类:数据与函数的结合,二者称为类的属性
类:
类的定义:
class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄; camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia; def __init__(self,nickname,aggresivity,life_value): #盖伦英雄的名字,攻击力,生命值 self.nickname=nickname #别名 self.aggrv=aggresivity #攻击力 self.life_value=life_value #生命值
类的两种作用:实例化和属性引用
#!/usr/bin/env python #-*-coding:utf-8-*- class Garen: camp='Demacia' def __init__(self,nickname,aggresivity,life_value): self.nickname=nickname self.aggrv=aggresivity self.life_value=life_value def attack(self,enemy): #攻击技能,enemy是敌人 print('is attacking',self,enemy) #类的功能一:实例化(__init__与self)
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
g1=Garen('草丛伦',80,120) #Garen.__init__(g1,'草丛伦',80,120) #g1=self,nickname='草丛伦',aggresivity=80,life_value=120
#实例调用
g1.attack(3) #Garen.attack(g1,'a')
#输出结果:
#is attacking <__main__.Garen object at 0x0000000002679C50> 3
#类功能二:属性引用(类名.属性),包括数据属性和函数属性 #数据属性: print(Garen.camp) #输出结果:Demacia # #函数属性: print(Garen.__init__) print(Garen.attack) #输出结果: #<function Garen.__init__ at 0x000000000252B840> #<function Garen.attack at 0x000000000252B8C8>
类的总结:
#类的定义: class 类名: '''文档描述''' x=1#变量定义 def __init__(self): pass #属性引用 类名.xxx #实例化 对象=类名(参数)----》类名.__init__(对象,参数)
二. 注意python2.x与python3.x的区别
大前提: 1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类 3.所有类甭管是否显式声明父类,都有一个默认继承object父类(讲继承时会讲,先记住) 在python2中的区分 经典类: class 类名: pass 经典类: class 类名(父类): pass 在python3中,上述两种定义方式全都是新式类
类的属性的补充:
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
对象:
单独说一下对象,借助上一个例子
g1=Garen('草丛伦') #类实例化得到g1这个实例,基于该实例看下对象
实例/对象只有一个属性:
#对于一个实例来说,只有一个功能:属性引用,实例本身只拥有数据属性 print(g1.nickname) print(g1.aggrv) print(g1.life_value) 输出结果: 草丛伦 80 120
对象补充:
查看实例属性 同样是dir和内置__dict__两种方式 特殊实例属性 __class__ __dict__
对象/实例本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法
#实例可以调用类的数据属性和函数属性 print(g1.camp) print(g1.attack) #对象的绑定方法
#输出结果:
# Demacia
# <bound method Garen.attack of <__main__.Garen object at 0x0000000002269CC0>>
#绑定方法把对象本身当做第一个参数穿进去
#类的调用: 相当于函数 Garen.attack(1,2) # 输出结果: # is attacking 1 2
对象之间的交互:
class Garen: camp='Demacia' def __init__(self,nickname,aggresivity,life_value): self.nickname=nickname self.aggrv=aggresivity self.life_value=life_value def attack(self,enemy): print('is attacking',self,enemy) class Riven: camp='Noxus' def __init__(self,nickname,aggresivity,life_value): self.nickname=nickname self.aggrv=aggresivity self.life_value=life_value def attack(self,enemy): print('is attacking',self,enemy) enemy.life_value-=self.aggrv #g1.life_value-=r1.aggrv 根据盖伦的攻击力干掉瑞文的生命值 g1=Garen('草丛伦',80,100) r1=Riven('瑞文',60,200) print(g1.life_value) r1.attack(g1) print(g1.life_value) 输出结果: 100 is attacking <__main__.Riven object at 0x00000000021A9E10> <__main__.Garen object at 0x00000000021A9DD8> 40
类的名称空间:
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
类有两种属性:数据属性和函数属性
其中类的数据属性是共享给所有对象的
print(id(g1.camp)) print(id(Garen.camp)) 输出结果: 34575448 34575448
而类的函数属性是绑定到所有对象的:
print(id(g1.attack)) print(id(Garen.attack)) 输出结果: 31839048 40155336
总结:创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在Garen里面找camp,会先从__init__中找,camp,如果没有会在类中去找,如果还没有的话,就到父类中去找,一直把所有的父类都找完,还没有的话,就会抛出异常。
第四:继承与派生
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
python分为单继承和多继承
定义的方式:
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承:
print(SubClass1.__bases__) (<class '__main__.ParentClass1'>,) print(SubClass2.__bases__) (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
补:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
print(ParentClass1.__bases__) (<class 'object'>,) print(ParentClass2.__bases__) (<class 'object'>,)
eg:
class game: def __init__(self,name,user_num): self.name=name self.user_num=user_num class WZRY(game): pass class ZT(game): pass t1=WZRY('wzry',10000000000) t2=ZT('zhengtu',800000) print(t1.user_num) print(t2.user_num) print(game.__bases__) print(WZRY.__bases__) # 输出: # 10000000000 # 800000 #(<class 'object'>,) # (<class '__main__.game'>,)
小结:当我门在开发程序的过程中,如果我们定义了一个类,然后又想新建立另外一个类,但是类B的大部分内容与类A的相同时,我们就需要用到继承的方法了。
需要注意:子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),但是一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。eg:
class game: x=30 def __init__(self,name,user_num): self.name=name self.user_num=user_num class WZRY(game): x=3000#覆盖父类的值 def __init__(self,name,user_name,user_id): game.__init__(self,name,user_name) self.user_id=user_id # 在子类里面派生新功能 class ZT(game): pass t1=WZRY('wzry',10000000000,2) t2=ZT('zhengtu',800000) print(t1.x) #覆盖父类的值 print(t1.user_id)#派生出来的 # 输出: # 3000 # 2
继承实现的原理:
继承顺序:
class A(): 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中才分新式类与经典类
2 继承原理(python如何实现的继承)
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.如果对下一个类存在两个合法的选择,选择第一个父类
3.子类调用父类的方法:采用super
class game: x=30 def __init__(self,name,user_num): self.name=name self.user_num=user_num class WZRY(game): x=3000#覆盖父类的值 def __init__(self,name,user_name,user_id): super().__init__(name,user_name) self.user_id=user_id # 在子类里面派生新功能 class ZT(game): pass t1=WZRY('wzry',10000000000,2) t2=ZT('zhengtu',800000) print(t1.user_num) print(t2.user_num)
输出结果:
10000000000
800000
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
第五:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合案例:用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如老师教课程,老师有生日:
又可以分为多对一和多对多:
多对一:eg:
class Teacher: def __init__(self,name,course_obj): self.name=name self.course=course_obj class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price p_obj=Course('Python','5m','4000') t1=Teacher('bob',p_obj) t2=Teacher('mary',p_obj) print(t1.course.price) print(t2.course.price) #输出结果: 4000 4000
实例化几个老师都可以,呈现出多对一的关系
多对多:eg:
class Teacher: def __init__(self,name): self.name=name class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price class Teacher_To_Course: def __init__(self,teacher_obj,course_obj): self.course_obj=course_obj self.teacher_obj=teacher_obj t1=Teacher('bob') t2=Teacher('mary') t3=Teacher('cy') course1=Course('python',3,4000) course2=Course('openstack',4,5000) course3=Course('hadoop',2,3000) Relation1=Teacher_To_Course(t1,course1) Relation2=Teacher_To_Course(t2,course1) Relation3=Teacher_To_Course(t3,course1) Relation4=Teacher_To_Course(t1,course2) print(Relation1.course_obj.name) #bob-->python print(Relation2.course_obj.name) #mary-->python print(Relation3.course_obj.name)#cy--->python print(Relation4.course_obj.name)#bob---openstack #输出结果: python python python openstack
结果的输出可根据自己需要定义,也可以格式化程{'bob':['python','openstack','hadoop'],。。。。}。
小结:继承:通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如人是动物
组合:建立了类与组合的类之间的关系,它是一种‘有’的关系,比如人有生日
第六:接口与归一化设计
从继承中看接口:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类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('硬盘数据的读取方法') class Process(Interface): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') t=Txt() s=Sata() p=Process() t.read() t.write() s.read() s.write() p.read() p.write() # 输出: # 文本数据的读取方法 # 文本数据的读取方法 # 硬盘数据的读取方法 # 硬盘数据的读取方法 # 进程数据的读取方法 # 进程数据的读取方法
小结:实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
其二:为什么要有接口?
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化?就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
第七:抽象类
什么是抽象类?
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类?
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
在python中实现抽象类:
import abc class Interface(metaclass=abc.ABCMeta):#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 @abc.abstractmethod def read(self): #定接口函数read pass @abc.abstractmethod def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') t=Txt() t.read() t.write() 输出: 文本数据的读取方法 文本数据的读取方法
4. 抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
第八:多态与多态性
8.1:多态
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
1. 序列类型有多种形态:字符串,列表,元组。
2. 动物有多种形态:人,狗,猪
class Animal: def __init__(self,name,age,sex,species): self.name=name self.age=age self.sex=sex self.species=species def walk(self): print('walking %s' % self.species) def eat(self): print('eating %s' % self.species) class Pig(Animal): pass class People(Animal): pass class Dog(Animal): pass pig1=Pig('chao',3,'female','猪') pig2=Pig('chao2',3,'female','猪') peo1=People('ling',18,'male','人') dog=Dog('ya',4,'male','狗') def func(animal): # 提现多态性 animal.walk() func(pig1) func(peo1) func(dog)
8.2:多态性
什么是多态性?(注意:多态与多态性是两种概念)
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
>>> def func(animal): #参数animal就是对态性的体现 ... animal.talk() ... >>> people1=People() #产生一个人的对象 >>> pig1=Pig() #产生一个猪的对象 >>> dog1=Dog() #产生一个狗的对象 >>> func(people1) say hello >>> func(pig1) say aoao >>> func(dog1) say wangwang
综上我们也可以说,多态性是‘一个接口(函数func),多种实现(如f.click())
二.为什么要使用多态性?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
结合多态的例子扩展:
class Animal: def __init__(self,name,age,sex,species): self.name=name self.age=age self.sex=sex self.species=species def walk(self): print('walking %s' % self.species) def eat(self): print('eating %s' % self.species) class Pig(Animal): pass class People(Animal): pass class Dog(Animal): pass class Cat(Animal): pass pig1=Pig('chao',3,'female','猪') pig2=Pig('chao2',3,'female','猪') peo1=People('ling',18,'male','人') dog=Dog('ya',4,'male','狗') cat=Cat('kafeimao',8,'female','猫') def func(animal): # 提现多态性 animal.walk() func(pig1) func(peo1) func(dog) func(cat) 输出信息: walking 猪 walking 人 walking 狗 walking 猫
第九:封装
封装是做什么?
1.数据的封装
2.方法的封装
为什么要封装?
1.封装数据的主要原因是:保护隐私
2.封装方法的主要原因是:隔离复杂度
封装分为两个层面:
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面的封装:创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
#!/usr/bin/env python #-*-coding:utf-8-*- class Garen: camp='Demacia' def __init__(self,nickname,aggresivity,life_value): self.nickname=nickname self.aggrv=aggresivity self.life_value=life_value def attack(self,enemy): #攻击技能,enemy是敌人 print('is attacking',self,enemy) g1=Garen('草丛伦',80,120) print(g1.aggrv) print(Garen.camp) 输出结果: 80 Demacia
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
#!/usr/bin/env python #-*-coding:utf-8-*- class Garen: __camp='Demacia' #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__camp,会变形为_Garen__camp def __init__(self,nickname,aggresivity,life_value): self.__nickname=nickname #变形为self._A__X self.__aggrv=aggresivity self.__life_value=life_value def __bize(self): print('is bize') def __attack(self): self.__bize() #只有在类的内部才可以直接调用 g1=Garen('草丛伦',80,120) # 第一:这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了 print(g1._Garen__aggrv) print(g1._Garen__camp) print(Garen._Garen__camp) # 输出结果: # 80 # Demacia # Demacia # 第二:变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形 print(g1.__dict__) g1._Garen__aggrv=100 print(g1._Garen__aggrv) #输出结果: # {'_Garen__nickname': '草丛伦', '_Garen__life_value': 120, '_Garen__aggrv': 80} # 100 # 第三:在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的 #正常情况 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
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
2.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了 也可以用6.4所讲的特性property来解决
另外:
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),但是外部也可以用,不过不好看
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__
特性:
什么是特性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) #同上 # 注意:此时的area和perimeter不能被赋值的 # 如: c.perimeter=1000 报错: AttributeError: can't set attribute
特性的用途?
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友或者子类公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
换句话说就是:让成员函数通过属性方式调用
class C(): def __init__(self): self._x = 88 def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") a = C() print(C.x.__doc__) #打印doc print(a.x) #调用a.getx() a.x = 100 #调用a.setx() print(a.x) del a.x #调用a.delx() print(a.x) #已被删除,报错 输出结果: I'm the 'x' property. 88 100 AttributeError: 'C' object has no attribute '_x'
用property装饰器实现property函数的功能
class C(): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x a = C() print(C.x.__doc__) #打印doc print(a.x) #调用a.getx() a.x = 100 #调用a.setx() print(a.x) # del a.x #调用a.delx() print(a.x) #已被删除,报错 输出结果: # I'm the 'x' property. # None # 100 #AttributeError: 'C' object has no attribute '_x'
另一种方式
class Foo: def get_AAA(self): print('get 执行我') def set_AAA(self,value): print('set 执行我') def delete_AAA(self): print('delete 执行我') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA 输出结果: get 执行我 set 执行我 delete 执行我
第十:静态方法和类方法
扩充:
Python中的方法有4种:
1)模块中的全局方法,不属于任何类,用"模块名.方法名"形式调用。
2)类中定义的实例方法,也被称为绑定方法(Bound method),这种方法的第一个参数(通常写作self,类似Java和C++的this),必须是调用该方法的实例本身,调用该方法时Python会自动将那个实例传递给该方法。
3)类中定义的静态方法,与Java中的静态方法一样,无需任何实例作为该方法的参数,用"类名.方法名"形式即可调用(用"实例.方法名"形式也能调用),定义方法时,需要用@staticmethod修饰符进行修饰。
4)还有一种方法叫类方法,也是在类中定义的,用类名和实例都能调用(即"类名.方法名"、"实例.方法名"),类方法的第一个参数(通常是cls),必须是包含这个方法的那个类,定义方法时,需要用@classmethod修饰符进行修饰,调用该方法时,Python会自动将该类作为参数传递给类方法。
咱们这里看静态方法和类方法:
静态方法:
是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,python为我们内置了函数staticmethod来把类中的函数定义成静态方法
class Foo: def spam(x,y,z): #类中的一个函数,千万不要懵逼,self和x啥的没有不同都是参数名 print(x,y,z) spam=staticmethod(spam) #把spam函数做成静态方法
基于之前所学装饰器的知识,@staticmethod 等同于spam=staticmethod(spam),于是
class Foo: @staticmethod #装饰器 def spam(x,y,z): print(x,y,z)
类方法
类方法是给类用的,类在使用时会将类本身当做参数传给类方法的第一个参数,python为我们内置了函数classmethod来把类中的函数定义成类方法
class A: x=1 @classmethod def test(cls): print(cls,cls.x) class B(A): x=2 B.test() ''' 输出结果: <class '__main__.B'> 2 '''
示例:
#!/usr/bin/env python # -*- coding: utf-8 -*- class MyClass(): name='' def __init__(self,name): self.name=name def foo(self,x): print('calling bound method foo() by instance "%s",x=%s'%(self.name,x)) @staticmethod #静态方法修饰符 def fooStatic(z): print('calling static method fooStatic(),z=%s'%z) @classmethod #类方法修饰符 def fooCls(cls,y): print('calling class method fooCls() by class "%s",y=%s' % (cls,y)) if __name__=='__main__': print('-----------下面开始调用实例方法-------------') mc=MyClass('zl') mc.foo(5) print('-----------下面开始调用类方法---------------') MyClass.fooCls(10) mc.fooCls(10) print('-----------下面开始调用静态方法-------------') MyClass.fooStatic(15) mc.fooStatic(15) 输出结果: -----------下面开始调用实例方法------------- calling bound method foo() by instance "zl",x=5 -----------下面开始调用类方法--------------- calling class method fooCls() by class "<class '__main__.MyClass'>",y=10 calling class method fooCls() by class "<class '__main__.MyClass'>",y=10 -----------下面开始调用静态方法----------- calling static method fooStatic(),z=15 calling static method fooStatic(),z=15