我们已经学习过面向对象编程的类、对象之间的关系等等, 接下来我们要深入学习如何具体控制属性、方法来满足需要,完成功能。
- 私有化属性
- 私有化方法(封装,加一个锁)
- property属性
- __new__方法
- 单例模式
- 错误与异常处理
- python动态添加属性和方法(对象.属性=XX)
- __slots__属性(限制属性)
目标:
- 通过声明私有化属性、方法,保护和控制数据(重点)
- 通过property属性的使用,即控制好数据又方便访问(重点、难点)
- 明确__new__方法的作用和方法(重点)
- 通过单例模式,控制实例个数(难点)
- 使用异常处理机制,处理异常,提高代码健壮性
- 利用动态语言特性,动态添加属性和方法
- 利用__slots__属性控制可动态的属性
一、私有化属性
修改类的属性都是直接通过类的属性进行修改的,如果有些重要属性不想别人随便修改,或者防止意外修改,该怎么办? 为了更好的保存属性的安全,即不能随意修改,将属性定义为私有属性(私有化属性),提供一个公开的入口让大家去访问(添加一个可调用的方法去访问)。 别人不能修改,但是可以访问。
使用私有化属性的情形:
- 想保护这个属性,不想让属性的值随意修改;
- 把特定的一个属性隐藏起来,不想让类的外部进行直接调用;
- 想保护这个属性,不想让派生类【子类】随意去继承。
语法:两个下划线开头,声明该属性(类属性、实例属性)为私有,不能在类的外部被使用或直接地被访问。 属性私有化之后,不能在外部访问。当然,在类的内部可以访问。
class Person:
__hobby='跳舞' #类属性,私有化的,不能在类的外部访问,可以在类的内部访问
def __init__(self):
self.name='李四'
self.__age=24 #属性前面加两个下划线,将此属性私有化;私有化之后,不能在外部访问当然,在类的内部可以访问
pass
def __str__(self):
'''
私有化属性在内部可以使用 self.__age
:param self:
:return:
'''
return '{}的年龄是{},爱好是{}'.format(self.name,self.__age,self.__hobby) #私有属性 可以在类的内部访问
def changeValue(self):
Person.__hobby='唱歌' #在类的内部可以修改私有类属性的值
pass
#私有属性不能在外部被访问!!!!!
xl=Person()
print(xl.name) #李四。 在外部 可以访问共有属性name
print(xl.__age) #报错!在外部 不能访问私有属性__age
print(xl.__hobby) #报错!在外部不能访问私有化的 类属性
print(Person.__hobby)
xl.changeValue() #调用类里的函数,可以修改私有类属性的值
print(xl) #李四的年龄是24,爱好是唱歌
#子类里不可以访问父类内部的私有化属性__age,即私有属性不可以被子类继承!!!!!
class student(Person):
def printInfo(self):
print(self.__age) #在此访问父类的私有属性,可以吗? ---不可以
pass
stu=student()
# print(stu.__age) #报错!student没有属性__age,私有属性不能被继承
stu.printInfo() #报错!
小结:
- 私有化的【实例】属性 不能在类的外部直接访问 可以在类的内部随意的使用;
- 子类不能继承父类的私有化属性,只能继承父类公共的属性和行为;
- 私有化属性可以在类里面访问和修改;
- 在属性名面前直接加“__”就可以变成私有属性。
二、私有化方法
私有化方法和私有化属性的概念一样,有些重要的方法,不允许外部调用,防止子类意外重写(不想子类继承该方法),把普通的方法设置成私有化方法。私有化方法一般类内部调用,子类不能继承,外部不能调用。
语法: 在方法名前加两个下划线
class Animal:
def __eat(self): #私有方法,不允许在外部访问、调用和子类的继承
print('吃东西')
pass
def run(self):
self.__eat() #在类内部可以调用私有化方法
print('飞快地跑')
pass
pass
class Bird(Animal):
pass
b1=Bird() #创建子类对象---Birds
b1.run() #吃东西 飞快地跑!子类继承了父类的方法(公有方法)
b1.__ate() #报错!私有化方法__eat不能被子类继承和外部使用;但是在类的内部可以调用
# 如果一个父类中,有一个私有化方法A,我们在外部需要调用它,但是不能将其变成公有化方法,需要:在父类中增加一个实例方法B(公有的),在方法B里调用私有化方法A(self.A)。
- 前面下划线:__XXX私有化类型
- 单下划线开头:_XXX 表示 protected类型的变量,即保护类型,只能允许其本身与子类进行访问,不能使用from XXX import *的方式导入。
- 前后两个下划线:__XXX__,魔术方法(内置的),一般是python自带,开发者不要创建这类型的方法
- 后面单下划线: XXX_ ,避免属性名与python关键字冲突
property属性
前面知道,私有属性不能在外部进行访问、调用和继承。因此
为了访问私有属性的值,需要在类的内部定义一个返回私有属性的函数!
为了修改私有属性的值,需要在类的内部定义一个修改私有属性的函数!
#访问私有变量的话,一般写两个方法---一个访问,一个修改,由方法去控制访问。
class Person():
def __init__(self):
self.__age=18 #定义一个私有化属性
pass
def get_age(self): #访问私有化属性
return self.__age
def set_age(self,age): #修改私有化属性
if age<0:
print('年龄不能小于0')
pass
else:
self.__age=age
p1=Person()
# p1.get_age() #报错
p1.set_age(34)
p1.get_age()
但是这样给调用者的感觉就是调用了一个方法, 并不是访问属性(XXX.属性形式)!
那么我们怎么才能做到让调用者直接以访问属性的方式,而且我们又能控制的方法提供给调用者?
属性函数(pronperty)可以实现!!! 用属性函数 实现私有属性的 访问和修改!!
在上述代码后定义一个类属性:实现【通过直接访问属性】的形式去访问和修改私有属性:
方法一:property属性函数
class Person():
def get_age:
...
def set_age:
...
age=property(get_age,set_age) #定义一个属性,当对这个age设置值时调用set_age
#当获取值时调用get_age
#注意:必须是以get、set开头的方法名,才能被调用!
pass
#使用的时候不用调用对象的方法!!!! 调用一个age属性,age属性里是属性函数
p1=Person()
print(p1.age) # 访问属性age,自动调用get_age函数:输出18
p1.age=25
print(p1.age) # 访问属性age,自动调用set_age函数:输出25
p1.age=-1 # 输出:年龄不能小于0
方法二:装饰器,即在方法上使用装饰器
@property # 提供一个getter方法,访问私有属性的值
@age.setter # 提供一个setter方法,修改私有属性的值
class Person():
def __init__(self):
self.__age=18 #定义一个私有化属性
@property #用装饰器对age进行修饰 添加属性标志,提供一个getter方法
def age(self): #访问私有化属性
return self.__age
@age.setter #用装饰器对age进行修饰 提供一个setter方法
def age(self,parms): #修改私有化属性
if parms<0:
print('年龄不能小于0')
else:
self.__age=parms
pass
pass
p1=Person()
print(p1.age) # 访问属性age,自动调用age函数(property)
p1.age=30
print(p1.age) # 访问属性age,自动调用age函数(age.setter)
p1.age=-1
__new__方法
作用:创建并返回一个实例对象!
- 如果__new__只调用了一次就会得到一个对象
- __new__是一个静态方法
- __new__是在一个对象实例化的时候 所调用的第一个方法(在__init__方法执行前就执行了)
- __new__至少必须有一个参数cls,代表实例化的类,此参数在实例化时由python解释器自动提供,其他参数是用来直接传递给init方法
- __new__决定是否使用该__init__方法。因为__new__可以调用其他类的构造方法或直接返回别的实例化对象来作为本类的实例。如果__new__没有返回实例对象,则__init__方法就不会调用
- 实例化流程:先__new__,再__init__
- 在__new__方法中,不能调用自己的__new__方法。即:return cls.__new__(cls)会报错。
- 实例化的过程:实际上解释器会自动调用一个__new__方法,虽然上面没有写。
- 对象创建完成之后,才会返回给__init__方法
- 在新式类中,__new__才是真正的实例化方法,为类提供外壳制造出实例框架,然后调用该框架内的构造方法__init__
- 比喻建房子 :__new__ 方法负责开发地皮、打地基,并将原材料存放于工地;而__init__负责从工地取材料建造出地皮开发图纸规定的大楼,负责细节设计、建造 最终完成。
实现方法一:
class A(object):
def __init__(self):
print('__init__执行了')
pass
def __new__(cls, *args, **kwargs): #实际上是静态方法
print('__new__执行了') #首先执行
return object.__new__(cls) #创建真正的实例,调用父类(object)new方法
pass
a=A()
class Animal:
def __init__(self):
self.color='红色'
pass
tigger=Animal()
#实例化的过程:实际上解释器会自动调用一个__new__方法,虽然上面没有写;创建之后,才会返回给__init__方法
# 在python中,如果重写 __new__ ,默认结构如下:
def __new__(cls, *args, **kwargs):
return supper().__new__(cls,*args, **kwargs) #不能自己调用自己,必须是调用父类(object、supper())
# return object.__new__(cls,*args, **kwargs) #这样也可以,但是不能是cls.__new__(cls,*args, **kwargs)
pass
单例模式(__new__实现)
- 是一种常用的软件设计模式,目的:确保某一个类只有一个实例的存在
- 使用装饰器、模块、类、__new__方法都可以实现。
- 如果希望在某个系统中,某个类只能出现一个实例的时候,那么这个单例对象就满足要求。
- 单例模式就比如我们打开电脑的回收站,系统中只能打开一个回收站,也就说这个整个系统 中只有一个实例,重复打开也是这个实例。 简单的说就是不管创建多少次对象,类返回的对象都是最初创建的,不会再新建其他对象。
创建一个单例对象:基于__new__方法实现【推荐的一种】
class DataBaseClass(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'): #如果不存在类属性_instance的值就开始执行__new__方法;否则不执行__new__方法
# cls._instance=cls.__new__(cls) #容易造成一个深度递归,应该调用父类的new方法
cls._instance=object.__new__(cls, *args, **kwargs)
return cls._instance # 每一次都要返回!
pass
# 创建的两个对象的地址不一样----修改:只需要加一个判断
# 为了之后创建的对象都是一个地址,需要:_instance只要存在了,之后就不需要再创建了,保证只有一个_instance
class DBoptSingle(DataBaseClass):
pass
# 此时定义的每个对象都能返回相同的地址吗?----相同,子类创建的对象和父类创建的对象也是一个地址
db1=DataBaseClass()
print(id(db1))
db2=DataBaseClass()
print(id(db2))
db3=DataBaseClass()
print(id(db3))
dbs1=DBoptSingle()
print(id(dbs1))
#保证每次创建都是同一个对象:利用类属性保存初次创建的实例对象,第二次实例化的时候类属性是否有保存的实例对象,如果有就返回类属性保存的,如果没有就调用父类new方法保存的。
def __new__(cls, *args, **kwargs):
if not cls._instance: # 如果不存在_instance就开始创建;否则不创建
cls._instance = supper().__new__(cls, *args, **kwargs)
return cls._instance # 每一次都要返回!
else:
return cls._instance
实现方法二:只执行一次init方法,通过类变量进行标记[自学]
class SingleCase():
__instance=None #保存实例对象
__isinit=True #首次执行__init__方法标记
def __init__(self,name,age):
if SingleCase.__isinit:
self.name=name
self.age=age
SingleCase.__isinit=False
def __new__(cls, *args, **kwargs): #如果类属性__instance的值为None,那么新建一个对象
#如果类属性的值不为None,返回__instance保存的对象
if not cls.__instance:
cls.__instance=super().__new__(cls) #调用父类__new__方法生成一个实例对象
return cls.__instance
else:
return cls.__instance
pass
pass
sc1=SingleCase('s',12)
print(id(sc1))
sc2=SingleCase('sss',13)
print(id(sc2)) #一样的地址,是同一个对象
异常处理
- 一般情况下,代码一开始发现异常之后,直接中断程序; 如果对异常进行一个捕获(try)的操作,程序就不会中断,后面的会正常执行
- Exception 可以捕获所有的错误类型------当对出现的问题或者错误不确定的情况下,可以使用Exception。
def A(s):
print(s) #NameError类型
pass
def B():
li = [1, 2, 34] #IndexError类型;也可以使用Exception
print(li[10]) #通过下标访问列表
def main():
try:
A(b)
B()
pass
except NameError as mse: #NameError类型 #except一定和离他最近的try是一体的
#捕获到的错误才会在这里执行
print(mse)
pass
except Exception as result: #万能类型:可以捕获所有的错误类型
print(result) #不能忽略这些错误,必须打印出来---在此尽量的去处理捕获到的异常
pass
pass
# 不需要在每个可能出错的地方进行捕获,只要在合适的层次去捕获错误就可以了。大大减少了我们写try-expect的麻烦
main() #调用函数
- except在捕获错误异常类型时,只要根据具体的错误类型来捕获;可以打印看看有哪些错误类型
- 用一个块try可以捕获多个不同类型的异常
- 万能类型Exception:可以捕获所有的错误类型(语法错误捕获不了)
异常的抛出机制
- 在运行时发生异常,解释器会查找相应的异常捕获类型
- 如果在当前函数没有找到异常,他会将异常传递给上层的调用函数,看能否处理;
- 如果在最外层还没有找到的话,解释器就会退出,程序就会down哦!! 例如A(有错误,但仍然继续传递)---B----main(找到异常处理机制)
# try---except---else
try:
print('aa') # 正确
pass
except SyntaxError as msg:
print(msg)
except Exception as msg:
print('error',msg)
else:
print('当Try里面的代码 没有出现异常的情况下 我才会执行')
pass
#此时执行else中的,因为‘aa’是字符串,没有异常
try---except---finally
try:
int('ffff') #不合理的输出方式
pass
except Exception as msg:
print(msg)
pass
finally:
print('释放文件的资源、数据库连接时的资源等等时,才更有意义')
print('不管有没有出错都执行的代码块')
自定义异常
自定义异常:都要【直接或者间接继承Error或Exception类】。 由开发者主动抛出自定义异常,在python中使用raise关键字。
# 自定义的异常:
class TooLongException(Exception):
def __init__(self,leng):
self.len=leng
pass
def __str__(self):
return '您输入姓名数据的长度为'+str(self.len)+'长度超过了'
pass
# 异常检查机制:
def name_Test():
name=input('请输入姓名')
try: #捕获异常
if len(name)>5:
raise TooLongMyException(len(name)) #提示异常
else:
print(name)
pass
pass
except TooLongMyException as msg:
print(msg) #输出捕获的错误信息
pass
finally:
print('程序结束了')
name_Test()
动态添加属性和方法
动态语言:运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进, 已有的函数可以被删除或是其他结构上的变化。如:php、JavaScript,python都是动态语言,C,C#,JAVA都是静态语言。 所以python可以在程序运行过程中添加属性和方法。
运行中给【对象】动态的添加属性和方法
#动态添加实例方法需要导入库
import types
#动态添加实例方法:先定义要添加的方法
def dymicMethod(self):
print('{}的体重是{}kg,毕业于{}'.format(self.name,self.weight,self.school))
pass
#给类绑定类方法
@classmethod
def classTest(cls):
print('这是一个类方法')
pass
#给类绑定静态方法
@staticmethod
def staticMethod():
print('这是一个静态方法')
pass
#给定一个初始的类:
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
pass
def __str__(self):
return '{}今年{}岁了'.format(self.name,self.age)
pass
# ------------给类绑定类方法【类名.方法名=需要绑定类方法名】---------------
Student.TestMethod=classTest #给类绑定类方法
Student.TestMethod1() #类对象调用类方法,实例对象也可以
#------------实例对象调用类方法---------------
zyh=Student('张艳华',20)
print(zyh) #输出:张艳华今年20岁了
zyh.TestMethod() #实例对象调用类方法!输出:这是一个类方法
#------------给类绑定静态方法【类名.方法名=需要绑定的静态方法名】---------------
Student.TestMethod2=staticMethod #给类绑定静态方法
Student.TestMethod2() #类对象调用静态方法
#------------运行中给对象添加实例属性【属于某个特定实例的属性】---------------
zyh=Student('张艳华',20)
zyh.weight=80 #实例属性 通过 实例对象添加
print(zyh.weight) #输出:80
zm=Student('张明',25)
zm.weight #报错,对象zm没有添加weight属性
#在运行过程中想给对象zyh添加一个动态的属性-----其他对象不具备这个属性
#------------运行中给类添加属性【添加类属性:所有实例对象和类对象都会有的属性】--------------
Student.school='北京邮电大学' #类属性 通过类对象添加
print(zm.school) #输出:北京邮电大学
print(zyh.school) #输出:北京邮电大学
#------------动态的去添加实例方法---------------
#动态添加实例方法需要使用types库;先定义要添加的方法(dymicMethod);最后方法 绑定 对象
zyh.printInfo=types.MethodType(dymicMethod,zyh) #绑定哪个方法,给哪个对象绑定
zyh.printInfo() #调用动态绑定的方法
# zm.printInfo=types.MethodType(dymicMethod,zm)
# zm.printInfo() #报错!dymicMethod需要的weight属性张明没有
__slots__变量 属性限制
- python是动态语言,在运行时可以动态的添加属性。
- 如果要限制在运行时候给类添加属性:Python允许 在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性。
- 只有在__slots__变量中的属性才能被添加,没有在__slots__变量中的属性会添加失败。
- 可以防止其他人在调用类的时候胡乱添加属性和方法。
- __slots__属性子类不会继承,只有在当前类中有效。除非在子类中也声明这样一个__slots__变量才可以继承。
- 作用: 限制要添加的实例属性、节约内存资源(不在保存在字典里,用__dict__会报错)
注意:
- 没有定义__slots__变量之前,通过字典存储数据,占用的内存空间大;如果通过__slots__变量进行限制,数据就不会存在字典里,节约内存资源。
- 定义了__slots__变量之后,Student类的实例已经不能随意创建不在__slots__定义的属性了,同时还可以看到实例当中也不再有__dict__。
- 子类继承父类之后,子类的对象可以随意创建新的属性。
- 当子类未声明__slots__变量时,那么是不会继承父类的__slots__的,此时子类是可以随意给属性赋值/添加属性
- 子类声明了__slots__变量时,会继承父类的__slots__声明的变量。也就是子类__slots__的范围是 其自身限制的属性+父类限制的属性 。因此父类__slots__声明过的属性,在子类中不需要重新__slots__声明。
class Student():
__slots__ = ('name','age')
def __str__(self):
return '{}今年{}岁'.format(self.name,self.age)
pass
xw=Student()
xw.name='小王'
xw.age=32
# xw.score=45 #报错!! __slots__中只限制了name,age
print(xw)
print(xw.__dict__) #没有__slots__变量时:所有可以用的属性都在这里存储,耗费内存
class subStudent(Student):
__slots__=('gender','pro') #该类的限制属性:('name','age')+('gender','pro')
pass
ln=subStudent()
ln.gender='男'
ln.pro='计算机信息管理'
ln.age=18 #继承父类的age属性,除了这四个属性,不能添加其他属性。
print(ln.gender,ln.pro,ln.age)