Python面向对象编程
05、Python面向对象
5.1、oop介绍
面向对象编程 oop 【object oriented programming】是一种Python的编程思路
- 面向过程:
在思考问题的时候,怎么按照步骤去实现,
然后将问题解决拆分成若干个步骤,并将这些步骤对应成方法一步一步的最终完成功能 - 面向过程:就是一开始学习的,按照解决问题步骤去编写代码【根据业务逻辑去写代码】
- 面向对象:关注的是设计思维【找洗车店, 给钱洗车】
- 从计算机角度看:面向过程不适合做大项目
- 面向过程关注的是:怎么做
- 面向对象关注的是:谁来做
5.2、类和对象
-
类:是一个模板,模板里包含多个函数,函数里实现一些功能
-
对象:则是根据模板创建的实例,通过实例对象可以执行类中的函数
-
类由3部分构成:
- 类的名称:类名
- 类的属性:一组数据
- 类的方法:允许对进行操作的方法(行为)
例如:创建一个人类
事物的名称(类名):人(Person)
属性:身高,年龄
方法;吃 跑…
- 类是具有一组 相同或者相似特征【属性】和行为【方法】的一系列对象的集合
现实世界 计算机世界
行为------》方法
特征------》属性
- 对象:是实实在在的一个东西,类的具象化 实例化
- 类是对象的抽象化 而对象是类的实例
5.3、定义类
#定义类和对象
#类名:采用大驼峰方式命名
#创建对象
#对象名=类名()
'''
class 类名:
属性
方法
'''
#实例方法 :
# 在类的内部,使用def关键字可以定义一个实例方法,与一般函数 定义不同,类方法必须包含参数self【self可以是其他的名字,但是这个位置必须被占用】,且为第一个参数
#属性:在类的 内部定义的变量
#定义在类里面,方法外面的属性成为类属性,定义在方法里面使用self引用的属性称之为实例属性
class Person:
'''
对应人的特征
'''
name='小勇' #类属性
age=22 #类属性
'''
对应人的行为
'''
def __int__(self):
self.name = '小赵'#实例属性
def eat(self):#实例方法
print('狼吞虎咽的吃')
def run(self):#实例方法
print('飞快地的跑')
#创建对象【类的实例化】
xm=Person()
xm.eat()#调用函数
xm.run()
print('{}的年龄是{}'.format(xm.name,xm.age))
5.4、__init__方法
# 如果有n个这样对象 被实例化,那么就需要添加很多次实例属性,显然比较麻烦
class Person1:
def __init__(self):#魔术方法
'''
实例属性的声明
'''
self.name='小勇'
self.age=22
self.sex='男生'
def run(self):
print('跑太快了吧')
xy=Person1()
# xy.run()
print(xy.age)
5.5、self理解
- self和对象指向同一个地址,可以认为self就是对象的引用
- 在实例化对象时,self不需要开发者传参,Python自动将对象传递给self
- self只有类中定义实例方法的时候才有意义,在调用的时候不必传入相应的参数,
- 而是由解释器自动取指向
- self的名字时可以更改的,可以定义成其他的名字,只是约定俗成的定义成了self
- self指的是类实例对象本身
class Person:
def __init__(self,pro):
self.pro=pro
def geteat(s,name,food):
# print(self)
print('self在内存中的地址%s'%(id(s)))
print('%s喜欢吃%s,专业是:%s'%(name,food,s.pro))
zs=Person('心理学')
print('zs的内存地址%s'%(id(zs)))
zs.geteat('小王','榴莲')
5.6、魔术方法
魔术方法: __ xxx __
'''
__init__ 方法:初始化一个类,在创建实例对象为其赋值时使用
__str__方法 : 在将对象转换成字符串str(对象)测试的时候,打印对象的信息
__new__方法 : 创建并返回一个实例对象,调用了一次,就会得到一个对象
__class__方法 : 获得已知对象的类(对象__class__)
__del__方法: 对象在程序运行结束后进行对象销毁的时候调用这个方法,来释放资源
'''
class Animal:
def __init__(self,name,color):
self.name=name
self.color=color
print('--------init-------')
def __str__(self):
return '我的名字是%s,我的颜色为%s'%(self.name,self.color)
def __new__(cls, *args, **kwargs):
print('--------new-------')
return object.__new__(cls)
dog =Animal('旺财','黑色')
print(dog)
'''
__new__和__init__函数区别
__new__类的实例化方法:必须返回该实例 否则对象就创建不成功
__init__用来做数据属性的初始化工作,也可以认为是实例的构造方法,接收类的实例 self 并对其进行构造
__new__至少有一个参数是cls是代表要实例化的类,此参数在实例化时由Python解释器自动操作
__new__函数执行要早于__init__函数
'''
5.7、析构方法
'''
当一个对象被删除或者被销毁是,python解释器会默认调用一个方法,这个方法为__del__()方法,也被成为析构方法
'''
class Animals:
def __init__(self,name):
self.name=name
print('这是构造初始化方法')
def __del__(self):
print('当在某个作用域下面,没有被引用的情况下,解释器会自动调用此函数,来释放内存空间')
print('这是析构方法')
print('%s 这个对象被彻底清理了,内存空间被释放了'%self.name)
cat=Animals('猫猫')
# del cat #手动的清理删除对象
input('程序等待中......')
#当整个程序脚本执行完毕后会自动的调用_del_方法
# 当对象被手动摧毁时也会自动调用_del_方法
# 析构方法一般用于资源回收,利用_del_方法销毁对象回收内存资源
5.8、单继承
Python中展现面向对象的三大类型: 封装,继承,多
态
- 封装:值得是把内容封装到某个地方,便于后面的使用
他需要:
把内容封装到某个地方,从另外一个地方去到调用被封装的内容
对于封装来说,其实就是使用初始化构造方法将内容封装到对象中,然后通过对象直接或者self来获取被封装的内容 - 继承:和现实生活当中的继承是一样的,也就是子可以继承父的内容【属性和行为】(爸爸有的儿子有, 相反, 儿子有的爸爸不一定有),
- 所谓多态,定义时的类型和运行时的类型是不一样,此时就成为多态
class Animal:
def eat(self):
'''
吃
'''
print('吃')
def drink(self):
'''
喝
'''
print('喝')
class Dog(Animal):#继承Animal父类, 此时Dog就是子类
def wwj(self):
print('汪汪叫')
class Cat(Animal):
def mmj(self):
print('喵喵叫')
d1=Dog()
d1.eat()#继承了父类的行为
d1.wwj()
c1=Cat()
c1.drink()
c1.eat()
'''
对于面向对象的继承来说,其实就是将多个子类共有的方法提取到父类中,
子类仅仅需要继承父类而不必一一去实现
这样就可以极大提高效率,减少代码的重复编写,精简代码的层级结构 便于扩展
class 类名(父类):
pass
'''
5.9、多继承
class shenxian:
def fly(self):
print('神仙会飞')
class Monkey:
def chitao(self):
print('猴子喜欢吃桃子')
class SunWuKong(shenxian,Monkey):
pass
swk=SunWuKong()
swk.fly()
swk.chitao()
#当多个父类当中存在相同方法时候,
class D:
def eat(self):
print('D.eat')
class C(D):
def eat(self):
print('C.eat')
class B(D):
pass
class A(B,C):
pass
a=A()
# b=B()
a.eat()
print(A.__mro__)#可以现实类的依次继承关系 查找执行顺序
#在执行eaet方法时,顺序应该是
#首先到A里面去找,如果A中没有,则就继续去B类中去查找,如果B中没有,则去C中查找,
#如果C类中没有,则去D类中查找,如果还没有找到,就会报错
#A-B-C-D 也是继承的顺序
5.10、重写父类方法
- 所谓重写,就是子类中,有一个和父类相同名的方法,在子类中的方法会覆盖掉子类中同名的方法,
- 为什么要重写,父类的方法已经不满足子类的需要,那么子类就可以重写父类或者完善父类方法
class Father:
def smoke(self):
print('抽芙蓉王')
def drink(self):
print('喝二锅头')
class Son(Father):
#与父类的(抽烟)方法同名,这就是重写父类方法
def smoke(self):
print('抽中华')
#重写父类方法后,子类调用父类方法时,将调用的是子类的方法
son=Son()
son.smoke()
5.11、多态
- 所谓多态,定义时的类型和运行时的类型是不一样,此时就成为多态
#要想实现多态,必须有两个前提需要遵守:
'''
1.继承:多态必须放生在父类和子类之间
2.重写:子类重写父类的方法
'''
class Animal:
'''
基类[父类]
'''
def say(self):
print('我是一个动物'*10)
class Dark(Animal):
'''
子类【派生类】
'''
def say(self):
'''
重写父类方法
'''
print('我是一个鸭子'*10)
class Dog(Animal):
def say(self):
print('我是一只🐕'*10)
# dark=Dark()
# dark.say()
#统一的去调用
def commonInvoke(obj):
obj.say()
list=[Dark(),Dog()]
for item in list:
'''
循环调用函数
'''
commonInvoke(item)
5.12、类属性和实例属性
- 类属性:就是类对象拥有的属性,它被所有类对象的实例对象所共有,类对象和实例对象可以访问
- 实例属性:实例对象所拥有的属性,只能通过实例对象访问
class Student:
name ='黎明'#属于类属性,就是Student类对象所拥有
def __init__(self,age):
self.age=age #实例属性
lm=Student(18)
print(lm.name)
print(lm.age)
print(Student.name)#通过Student类对象访问name属性
# print(Student.age)#类对象不能访问实例属性
#类对象可以被类对象和实例对象共同访问使用的
#实例对象只能实例对象访问
5.13、类方法和静态方法
- 类方法的第一个参数是类对象cls,通过cls引用的类对象的属性和方法
- 实例对象的第一个参数是实例对象self,通过self引用的可能是类属性,也可能是 实例属性
,不过在存在相同名称的类属性和实例属性的情况下,实例属性的优先级更高 - 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用
class People:
country='china'
@classmethod #类方法 用classmethod进行修饰
def get_country(cls):
return cls.country#访问类属性
@classmethod
def change_country(cls,data):
cls.country=data#修改列属性的值,在类方法中
@staticmethod
def gatData():
return People.country
@staticmethod
def add(x,y):
return x+y
print(People.gatData())
p=People()
print(p.gatData())#注意:一般情况下,我们不会通过实例对象去访问静态方法
print(People.add(1,2))
# print(People.get_country())#通过类对象去引用
# p=People()
# print('通过实例对象访问',p.get_country())
# People.change_country('英国')
# print(People.get_country())
#由于静态方法主要来存放逻辑性的代码,本身和类以及实例对象没有交互
#也就是说,在静态方法中,不会涉及到类中方法和属性的操作
#数据资源能够得到有效的充分利用
import time
class TimeTest:
def __init__(self,hour,minute,second):
self.hour=hour
self.minute=minute
self.second=second
@staticmethod
def showTime():
return time.strftime('%H:%M:%S',time.localtime())
print(TimeTest.showTime())
# t=TimeTest(2,10,11)
# print(t.showTime())#没必要通过实例对象访问静态方法
5.14、私有化属性
-
语法:
- 两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问
-
使用私有化属性的场景
- 把特定的一个属性隐藏起来,不想让类的外部进行直接调用
- 我想保护这个属性,不想让属性的值随意的改变,
- 保护这个属性,不想让派生类【子类】去继承
-
要想访问私有变量一般是写两个方法,一个访问,一个修改,由方法去控制访问
class Person:
__age =18 #实例一个私有化属性,属性名字前面加两个下划线
‘’’
class Person:
__hobby = 'dance'
def __init__(self):
self.__name = '李四' # 加两个下划线将此属性私有化,不能再外部直接访问,在类的内部可以访问
self.age = 30
def __str__(self):
return '{}的年龄是{},喜欢{}'.format(self.__name, self.age, Person.__hobby) # 调用私有化属性
def change(self, hobby):
Person.__hobby = hobby
class Studeng(Person):
def printInfo(self):
# print(self.__name)#访问不了父类中的私有属性
print(self.age)
pass
xl = Person()
# print(xl.name)#通过类对象,在外部访问的
print(xl)
xl.change('唱歌') # 修改私有属性的值
print(xl)
stu = Studeng()
stu.printInfo()
stu.change('Rap')
print(stu)
# print(xl.hobby) # 通过实例对象访问类属性
# print(stu.hobby) # 通过实例对象访问类属性
# print(Person.hobby) # 通过类对象访问类属性
-
小结:
- 私有化的【实例】属性,不能在外部直接的访问,可以在类的内部随意的使用
- 子类不能继承父类的私有化属性,【只能继承父类公共的属性和行为】
- 在属性名的前面直接加__,就可以将其私有化
-
单下划线、 双下划线、 头尾双下划线三者区别
_xxx前面加下划线,以单下划线开头表示的是protected类型的变量,即保护类型只能允许其本身与子类进行访问,不能使用from xxx import *的方式导入
__xxx__前后两个下划线,魔术方法,一般是python自带的,开发者不要创建这类型的方法
xxx_后面单下划线,避免属性名与Python关键字冲突
5.15、私有化方法
- 概述
- 私有化方法跟私有化属性一样,有些重要的方法,不允许外部调用,防止子类意外重写,把普通的方法设置成私有化方法
- 私有化方法一般是类内部调用,子类不能继承外部不能调用
- 语法
class A:
def __myname(self): #在方法名前面加两个下划线
print(‘小明’)
class Animal:
def __eat(self):
print('吃东西')
def run(self):
self.__eat() # 在此调用私有化方法
print('飞快地跑')
class Bird(Animal):
pass
bird = Bird()
bird.run()
5.16、Property函数
# 属性函数 (property)
class Perons:
def __init__(self):
self.__age = 18
def ger_age(self):#访问私有实例属性
return self.__age
def set_age(self, age):#修改私有实例属性
if age < 0:
print('年龄太小')
else:
self.__age = age
#定义一个类属性,实现通过直接访问属性的形式去访问私有属性
age=property(ger_age,set_age)
p=Perons()
print(p.age)
p.age=-1
print(p.age)
5.17、__new__
方法
- new方法在init方法之前执行
class A(object):
def __init__(self):
print("__init__执行了")
def __new__(cls, args, **kwargs):
print("__new__执行了")
return object.__new__(cls) # 调用父类的new方法
a = A() # 实例化的过程会自动调用__new__方法取创建实例
运行结果如下:
5.18、单例模式
单例模式:是一种常用的软件设计模式
目的:确保某一个类只有一个实例存在
如果希望在某个系统中只能出现一个实例,那么单例对象就能满足要求
# 创建一个单例对象,基于__new__去实现
class DataBaseClass(object):
def __new__(cls, *args, **kwargs):
# cls._instance=cls.__new__(cls)不能使用自身的new方法,容易造成深度递归,应该调用父类的new方法
if not hasattr(cls, "_instance"): # 如果不存在,就开始创建
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
db1 = DataBaseClass()
print(id(db1))
db2 = DataBaseClass()
print(id(db2))
5.19、异常处理
不需要在每个可能出错的地方捕获,只要在合适的层次去捕获错误就可以
#语法格式:
try:
可能出错的代码块
except:
出错后执行的代码块
else:
没有出错的代码块
finally:
不管有没有错都执行的代码块
try-except
- except在捕获错误类型的时候,主要根据具体的错误类型来捕获的
- 用一个try可以捕获多个不同类型的异常
code 1:
try:
print(b) #这里是要捕获的代码
except NameError: #指定错误类型为NameRrror
#捕捉到错误会在这里执行
print("NameError")
code 2:
try:
print(b) #这里是要捕获的代码
except NameError as msg:
#捕捉到错误会在这里执行
print(msg)
code 3:
try:
li=[1,2,54,78] #这里是要捕获的代码
print(li[10])
a=10/0
except NameError as msg:
#捕捉到错误会在这里执行
print(msg)
except IndexError as msg:
print(msg)
except ZeroDivisionError as msg:
print(msg)
code 4:
try:
a=10/0
except Exception as msg:
print(msg)
code 5:
def A(s):
return 10/int(s)
def B(s):
return A(s)*2
def main():
try:
B("0")
except Exception as msg:
print(msg)
main()
#不需要在每个可能出错的地方捕获,只要在合适的层次去捕获错误就可以
try-except-else
try:
print (aa)
except Exception as msg:
print(msg)
else:
print("当try里面没有错误。else才会执行")
try-except-else-finally
try:
print (aa)
except Exception as msg:
print(msg)
else:
print("当try里面没有错误。else才会执行")
finally:
print("不过有没有错都执行")
自定义异常
- 自定义异常,都要直接或者间接的继承
Error
或Exception
类 - 由开发者主动抛出自定义异常,在python中使用
raise
关键字
class ToolsMyException(Exception):
def __init__(self, leng):
self.leng = leng
def __str__(self):
return "您输入的的姓名长度是"+str(self.leng)+"已经超过长度"
def name_Test():
name=input("请输入姓名")
try:
if len(name)>5:
raise ToolsMyException(len(name))
else:
print(name)
except ToolsMyException as result:
print(result)
finally:
print("执行完毕-------")
name_Test()