面向对象基础(下)
前面我们已经学习了面向对象编程的类、对象、类之间的关系等,接下来我们要深入学习如何具体控制属性、方法来满足需要,完成功能。
私有化属性(实例化属性和类属性)
- 为了更好的保存属性安全,即不能随意修改,将属性定义为私有属性,添加一个可调用的方法去访问。
- 两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。
- 示例:
class Person(object):
__age = 18 # 定义一个私有化属性,属性名字前面加两个下划线
- 特征:
1、私有化属性不能在类外面访问
class Person:
self.__age = 18 # 定义一个私有实例属性
print(Person.__age) # 类对象无法访问私有类属性
xm=Person()
print(xm.__age) # 报错,这也属于外部访问(实例对象无法访问私有属性)
2、私有化属性可以在类里面访问,修改。
class Person:
__age = 18 # 定义一个私有化属性,属性名字前面加两个下划线
def get_age(self): # 访问私有类属性
return Person.__age
def set_age(self, age): # 修改私有类属性
Person.__age = age
xiaoming = Person()
print(xiaoming.get_age())
xiaoming.set_age(20)
print(xiaoming.get_age())
3、子类不能继承私有化属性。
class Person:
def __init__(self):
self.__age = 18 # 定义一个私有实例属性
def get_age(self):
return self.__age # 访问私有实例属性
def set_age(self,age):
self.__age = age # 修改私有实例属性
class China(Person):
def get_age(self):
print(self.__age) # 尝试访问父类的私有实例属性
xiaoming = China()
xiaoming.get_age() # 报异常无法访问
私有化方法
- 私有化方法跟私有化属性概念一样,有些重要的方法,不允许外部调用,防止子类意外重写,把普通的方法设置成私有化方法。
- 私有化方法,即在方法名前面加两个下划线。
- 示例:
class A:
## 在方法前面加两个 __ 下划线,变成私有化方法
def __myname(self):
print('xiaoming')
#普通方法
def myname(self):
print('xiaoming')
a = A()
a.myname() # 正常调用
a.__myname() # 调用私有化方法,报错
- 私有化方法一般是类内部调用,子类不能继承,外部不能调用。
class A:
## 在方法前面加两个 __ 下划线,变成私有化方法
def __myname(self):
print('xiaoming')
def getname(self):
self.__myname() # 可以在类内部调用
a = A()
a.getname()
Property属性
目的:
之前为了访问私有属性,设置了私有方法,或者通过类里面定义方法的方法来访问和修改私有属性。增大了计算和编程的复杂度,所以引出Property属性函数
的方法,来像以前一样正常的访问私有属性。
案例1:
类属性,即在类中定义值为property对象的类属性
示例:给age属性设置值时,会自动调用set_age方法,获取age属性值时,会自动调用get_age方法。
class Person(object):
def __init__(self):
self.__age = 18 # 定义一个私有化属性,属性名字前加连个 __ 下滑线
def get_age(self): # 访问私有实例属性
return self.__age
def set_age(self,age): # 修改私有实例属性
if age < 0:
print('年龄不能小于0')
else:
self.__age = age
age = property(get_age, set_age) #定义一个类属性,实现通过直接访问属性的形式来访问私有属性
# 1、通过方法来访问私有属性
print('通过方法来访问私有属性')
p=Person()
print(p.get_age())
# 修改
p.set_age(15)
print(p.get_age())
# 2、定义一个类属性,实现通过直接访问属性的形式来访问私有属性
# 定义一个属性,当对这个age设置值时调用set_age,
# 当获取值时调用get_age
# 注意:必须是以get,set开头的方法名,才能被调用
print('实现通过直接访问属性的形式来访问私有属性')
xiaoming = Person()
print(xiaoming.age)
xiaoming.age = 15 # 修改
print(xiaoming.age)
案例2:
# class Person(object):
# def __init__(self):
# self.__age = 18 # 定义一个私有化属性,属性名字前加连个 __ 下滑线
# def get_age(self): # 访问私有实例属性
# return self.__age
# def set_age(self,age): # 修改私有实例属性
# if age < 0:
# print('年龄不能小于0')
# else:
# self.__age = age
# age = property(get_age, set_age) #定义一个类属性,实现通过直接访问属性的形式来访问私有属性
#
#
# # 1、通过方法来访问私有属性
# print('通过方法来访问私有属性')
# p=Person()
# print(p.get_age())
# # 修改
# p.set_age(15)
# print(p.get_age())
# # 2、定义一个类属性,实现通过直接访问属性的形式来访问私有属性
# # 定义一个属性,当对这个age设置值时调用set_age,
# # 当获取值时调用get_age
# # 注意:必须是以get,set开头的方法名,才能被调用
# print('实现通过直接访问属性的形式来访问私有属性')
# xiaoming = Person()
# print(xiaoming.age)
# xiaoming.age = 15 # 修改
# print(xiaoming.age)
class Person(object):
def __init__(self):
self.__age = 18 # 定义一个私有化属性,属性名字前加连个 __ 下滑线
@property # 使用装饰器对age进行装饰,提供一个getter方法
def age(self): # 访问私有实例属性
return self.__age
@age.setter # 使用装饰器进行装饰,提供一个setter方法
def age(self, age): # 修改私有实例属性
if age < 0:
print('年龄不能小于0')
else:
self.__age = age
xiaoming = Person()
print(xiaoming.age)
xiaoming.age = 15
print(xiaoming.age)
注意:
上面两种方法,推荐使用装饰器的方法,更加简便。
__new__
方法
作用:
创建并返回一个实例对象,如果__new__只调用了一次,就会得到一个对象。继承自object的新式类才有new这一魔法方法。
注意事项:
- __new__是在一个对象实例化的时候所调用的第一个方法
- __new__至少必须要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供 ,其他的参数是用来直接传递给 init 方法
- __new__决定是否要使用该 init 方法,因为 new 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 new 没有返回实例对象,则 init 不会被调用
- 在__new__ 方法中,不能调用自己的__new__ 方法,即:return cls.new (cls),否则会报错(RecursionError: maximum recursion depth exceeded:超过最大递归深度)
案例1:
class A:
def __init__(self):
print("__init__执行了")
def __new__(cls, *args, **kwargs):
print("__new__ 执行了")
return object.__new__(cls) # 调用父类的new方法
a = A()
案例2:
class Animal:
def __init__(self):
self.color = '红色'
pass
# # 默认new的编辑
# def __new__(cls, *args, **kwargs):
# return super().__new__(cls, *args, **kwargs)
# return cls.__new__(cls, *args, **kwargs) # 不能创建自己的new方法,会出错的
tigger = Animal() # 如果没有写new方法,实例化过程中会自动调用new的默认方法去创建实例
print(tigger.color)
小结:
在新式类中,__new__
才是真正的实例化的方法,为类提供外壳制造出实例框架,然后调用该实例框架内的构造方法__init__
,比喻建造房子:__new__
方法负责开发地皮,打地基,并将原材料存放在工地,而__init__
负责从工地取材料,负责细节的设计、建造以及最终的完成。
单例模式(一般都是new方法来实现的)
- 单例模式是常用设计模式的一种,单例就比如我们打开电脑的回收站,在系统中只能打开一个回收站,也就是说这个整个系统中只有一个实例,重复打开也是使用这个实例。
- 简单的说就是不管创建多少次对象,类返回的对象都是最初创建的,不会再新建其他对象。
案例1:
利用类属性保存初次创建的实例对象,第二次实例化的时候判断类属性是否有保存实例对象,如果有就返回类属性保存的,如果没有就调用父类__new__方法创建新的实例对象。
class DataBaseClass(object):
def __new__(cls, *args, **kwargs):
# 切记不能使用自身的new方法,容易造成一个深度递归,应该使用父类的方法
# cls._instance = cls.__new__(cls)
cls._instance = super().__new__(cls,*args,**kwargs)
return cls._instance
print('1、普通方法,调用两次是两个实例化对象')
db1 = DataBaseClass()
print(id(db1))
db2 = DataBaseClass()
print(id(db2))
# 创建单例对象(推荐使用__new__方法来实现)
class DataBaseClass(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'): # 如果不存在就开始创建
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
print('2、单例模式: 不管调用几次都是原来的那一个')
db1 = DataBaseClass()
print(id(db1))
db2 = DataBaseClass()
print(id(db2))
class DataBaseClass(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'_instance'): # 如果不存在就开始创建
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
class DBoptSing(DataBaseClass):
pass
print('3、继承单例模式: 不管调用几次都是原来的那一个')
db1 = DBoptSing()
print(id(db1))
db2 = DBoptSing()
print(id(db2))
案例2:
__init__只执行了一次
class SingleCase(object):
__instance = None # 保存实例对象
__isinit = True # 首次执行init方法标记
def __init__(self,name,age):
if SingleCase.__isinit:
self.name = name
self.age = age
SingleCase.__isinit = False
# 如果类属性 __instance 的值为None,那么新建一个对象
# 如果类属性值不为None 返回 __instance 保存的对象
def __new__(cls, *args, **kwargs):
if not cls.__instance:
cls.__instance = super(SingleCase, cls).__new__(cls) # 调用父类__new__方法生成一个实例对象
return cls.__instance
else:
return cls.__instance
obj1 = SingleCase('xiaoming',18)
obj2 = SingleCase('luban',118)
print(id(obj1))
print(id(obj2))
print(obj1.age)
print(obj1.name)
print(obj2.age) # 年龄都输出18。说明init值初始化了一次
print(obj2.name)
错误与异常处理
- 有时候代码写错了,执行程序的时候,执行到错误代码的时候,程序直接终止报错,这是因为Python检测到一个错误时,解释器就无法继续执行了,出现了错误的提示,这就是"异常"。
- 示例:变量b没有定义,直接打印变量b,会报异常
print(b)
print('上面出错这一行就不会再执行,用户体验很差')
try … except 语句:
将可能出错的代码放到try里面,except可以指定类型捕获异常。except里面的代码是捕获到异常时执行,将错误捕获,这样程序就不会因为一段代码包异常而导致整个程序崩溃
try:
print(b)
pass
except NameError as msg:
# 捕获到的错误才会在这里执行
print(msg)
pass
print('上面出错,如果被捕捉到(错误类型对的话),这样还是会执行的')
try:
li=[1,2,3]
print(li[10]) # 超出索引
pass
except NameError as msg:
# 捕获到的错误才会在这里执行
print(msg)
pass
except IndexError as msg:
print(msg)
pass
print('except在捕获错误异常的时候,只要根据具体的错误类型来捕获,同一个try可以用多个except')
try:
a=10/0
pass
except Exception as msg:
print(msg)
pass
print('这是一个万能的异常捕获,如果不知道具体是什么错误类型,就用Exception')
try … except … else语句:没有捕获到异常时才执行else语句
try:
print('--------test----------')
except Exception as e: # Exception 可以捕获任何类型的异常
print(e)
else:
print('haha --- 没有捕获到异常') #没有捕获到异常,将执行else里面代码,否则不执行
try:
print('--------test----------')
e=1/0
except Exception as e: # Exception 可以捕获任何类型的异常
print(e)
else:
print('haha --- 捕获到异常所以不执行') #捕获到异常不执行else的代码
try … except … finally语句
try:
print('--------test----------')
except Exception as e: # Exception 可以捕获任何类型的异常
print(e)
else:
print('没有捕获到异常,将执行else里面代码,否则不执行 ')
finally:
print('不管有没有捕获到异常,finally都是执行的(常用于释放那个资源)')
异常的传递:
- 如果多个函数嵌套调用,内层函数异常,异常会往外部传递,直到异常被抛出,或被处理。
- 不需要再每一个可能出错的地方去捕获,只要在合适的层次去捕获即可;大大减少我们我们写try的麻烦。
print('不需要再每一个可能出错的地方去捕获,只要在合适的层次去捕获即可;大大减少我们我们写try的麻烦')
def A(s):
return 10/int(s)
def B(s):
return A(s)*2
def main():
try:
B('0')
pass
except Exception as msg:
print(msg)
main()
def a():
print('执行a函数')
1/0 # 制造一个异常
print('a函数执行完成')
def b():
print('执行b函数')
a() # 调用a函数
print('b函数执行完成')
def c():
print('执行c函数')
try:
b() # 调用b函数
except Exception as e:
print(e)
print('c函数执行完成')
c()
自定义异常:
- 自定义异常,都要直接或间接继承Error或Exception类。
- 由开发者主动抛出自定义异常,在python中使用raise关键字,
class ToolongMyException(Exception):
def __init__(self,long):
'''
:param long: 长度
'''
self.len = long
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
except ToolongMyException as msg: # 自定义异常,也可以用万能的
print(msg)
name_test()
动态添加属性和方法
- 动态语言:运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。如php,JavaScript,python都是动态语言,C,C#,java是静态语言。
- 所以python可以在程序运行过程中添加属性和方法。
动态添加属性
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
pass
def __str__(self):
return '{}今天{}岁了。'.format(self.name,self.age)
xm=Student('小明',15)
print(xm)
# 动态添加属性
print('***********给实例对象添加属性***********')
xm.weight = 60
print(xm.weight)
print('***********另一个实例对象的添加,并不具有weight的属性***********')
xh = Student('小红',20)
print(xh)
# print(xh.weight) # 报错
print('***********给类对象添加属性***********')
Student.school = '重庆邮电大学'
print(xm.school)
print(xh.school)
动态添加方法
1、动态添加实例方法需要使用types
# 动态添加实例方法
import types
def dynicNethod(self):
print('{}的体重是{}kg,在{}读研究生'.format(self.name,self.weight,Student.school))
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
pass
def __str__(self):
return '{}今天{}岁了。'.format(self.name,self.age)
xm=Student('小明',15)
print(xm)
Student.school = '重庆邮电大学'
xm.weight = 60
xm.dynicNethod = types.MethodType(dynicNethod,xm) # 将外部方法动态绑定在函数里面
# 执行动态绑定的方法
xm.dynicNethod()
print(xm)
2、给类绑定类方法和静态方法:使用方式:类名.方法名 = xxxx
# 动态添加实例方法(类方法绑定)
@classmethod # 动态类方法
def classTest(cls):
print('这是一个类方法')
@staticmethod # 静态类方法
def staticMethod():
print('这是一个静态方法')
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
pass
def __str__(self):
return '{}今天{}岁了。'.format(self.name,self.age)
print('绑定类方法')
Student.classTest = classTest
print('执行类方法')
Student.classTest()
print('实例对象调用动态绑定类方法')
xm=Student('小明',15)
xm.classTest()
print('绑定静态方法')
Student.staticMethod = staticMethod
Student.staticMethod()
__slots__属性
- python是动态语言,在运行的时候可以动态添加属性。如果要限制在运行的时候给类添加属性,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性。
- 只有在__slots__变量中的属性才能被添加,没有在__slots__变量中的属性会添加失败。可以防止其他人在调用类的时候胡乱添加属性或方法。__slots__属性子类不会继承,只有在当前类中有效。
作用:
限制要添加的类;节约内存空间
class Student():
__slots__ = ('name','age') # 只允许创建name和age
def __str__(self):
return '{}.....{}'.format(self.name,self.age)
pass
xw=Student()
xw.name = '小王'
xw.age = 20
# xw.school = '重邮' # 添加失败
# print(xw.__dict__) # 不用slots限定的话,所有可用的属性都在这里存储,暂用内存较大
print(xw)
print('在继承中的使用:子类未声明')
class subStudent(Student):
pass
xh = subStudent()
xh.name = '小红'
xh.age = 13
xh.pro = '信管'
print(xh.name,xh.age) # 父类中限定的两个
print(xh.pro) # 不受父类slots的限制
print('在继承中的使用:子类声明要继承(自己与父类的并集)')
class subStudent(Student):
__slots__ = ('pro') # 子类声明要继承,并且也可以自己在父类的基础上自己在加限制天添加
pass
xh = subStudent()
xh.name = '小红'
xh.age = 13
xh.pro = '信管'
print(xh.name,xh.age) # 父类中限定的两个
print(xh.pro) # 在继承父类的基础上,自己添加的
作业:
案例1:
'''
1、编写一段代码以完成下面的要求
定义一个Person类,类中要有初始化方法,方法中要有人的姓名,年龄两个私有属性.
提供获取用户信息的函数.
提供获取私有属性的方法.
提供可以设置私有属性的方法.
设置年龄的范围在(0-120)的方法,如果不在这个范围,不能设置成功.
'''
class Person():
def __init__(self,name,age):
self.__name = name
self.__age = age
pass
# 提供获取用户信息的函数.
def __str__(self):
return '{}的年龄是{}'.format(self.name,self.age)
# 提供获取私有属性的方法.
def getage(self):
return self.__age
def getname(self):
return self.__name
# 提供可以设置私有属性的方法.
def setname(self):
self.__name = name
pass
def setage(self):
if age>0 and age<120:
self.__age = age
else:
print('你的输入不合法')
案例2:
'''
2、创建一个类,并定义两个私有化属性,提供一个获取属性的方法,
和设置属性的方法。
利用property 属性给调用者提供属性方式的调用获取和设置私有属性方法的方式。
'''
class Student():
def __init__(self):
self.__name = '小张'
self.__age = 15
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
pass
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
self.__age = age
pass
def __str__(self):
return '{}的年龄是{}'.format(self.__name,self.__age)
def __call__(self, *args, **kwargs): # # 将实例对象以函数形式调用
print('{}的年龄是{}'.format(self.__name,self.__age))
xz=Student()
print(xz)
xz() # 将实例对象以函数形式调用
print('修改')
xz.name='李四'
xz.age = 25
xz()
案例3:
'''
3、创建一个Animal类,实例化一个cat对象,请给cat对象动态绑定一个run方法,
给类绑定一个类属性color,给类绑定一个类方法打印字符串'ok'。
'''
import types
def run(self):
print('小猫爬上树')
pass
@classmethod
def info(cls):
print('OK')
class Animal:
pass
Animal.color = '黄色' # 绑定类属性
Animal.info = info # 绑定类方法
cat=Animal()
cat.run = types.MethodType(run,cat) # 动态绑定
cat.run()
print(cat.color)
Animal.info()