Python面向对象OOP
面向对象的三大属性:
- 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
- 继承 实现代码的重用,相同的代码不需要重复的编写
- 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
1. 创建对象时自动调用初始化方法
- 当使用类名()创建对象时,为对象分配空间后,自动调用init方法
class Cat:def __init__(self):print('这是一个初始化方法!')# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)tom = Cat()

2.在初始化方法内部定义属性
class Cat:def __init__(self):self.name = 'Tom'print('这是一个初始化方法!')# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)tom = Cat()print(tom.name)

3.初始化同时设置初始值
class Cat:def __init__(self,name):self.name = nameprint('这是一个初始化方法!')# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)tom = Cat('tom')print(tom.name)

4.内置方法del方法和对象生命周期
当一个对象被从内存中销毁前,会调用del方法。
__init__改造初始化方法,可以让创建对象更加灵活__del__如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__方法
生命周期
- 一个对象从调用
类名()创建,生命周期开始 - 一个对象的
__del__方法一旦被调用,生命周期结束 - 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
class Cat:def __init__(self,name):self.name = nameprint('这是一个初始化方法!')def __del__(self):print('这个对象被销毁了')# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)tom = Cat('tom')print(tom.name)del tom # del关键字可以删除一个对象

5.内置方法str方法定制变量输入信息
class Cat:def __init__(self,name):self.name = nameprint('这是一个初始化方法!')def __del__(self):print('这个对象被销毁了')# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)tom = Cat('tom')print(tom.name)print(tom)del tom
当不自定义str方法时,直接打印对象效果为:

可以看到打印出来的是main.类名 object at 对象的内存地址
当我们自定义了之后:
class Cat:def __init__(self,name):self.name = nameprint('这是一个初始化方法!')def __del__(self):print('这个对象被销毁了')def __str__(self):return '我最帅'# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)tom = Cat('tom')print(tom.name)print(tom)del tom
打印出了我们设置的返回的内容。
6.封装特性和案例
1.封装的概念
- 封装 是面向对象编程的一大特点
- 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
- 外界 使用 类 创建 对象,然后 让对象调用方法
- 对象方法的细节 都被 封装 在 类的内部
2.小明爱跑步案例
class Person:def __init__(self,name,weight):self.name = nameself.weight = weightdef __str__(self):return f'我的名族叫:{self.name},我的体重是:{self.weight}公斤'def run(self):print(f'{self.name}爱跑步')self.weight -= 0.5def eat(self):print(f'{self.name}爱跑步')self.weight += 1xiaoming = Person('小明',75.0)print(xiaoming)xiaoming.run()print(xiaoming)xiaoming.eat()print(xiaoming)

3.多个对象之间互不干扰
class Person:def __init__(self,name,weight):self.name = nameself.weight = weightdef __str__(self):return f'我的名族叫:{self.name},我的体重是:{self.weight}公斤'def run(self):print(f'{self.name}爱跑步')self.weight -= 0.5def eat(self):print(f'{self.name}爱跑步')self.weight += 1xiaoming = Person('小明',75.0)xiaomei = Person('小美',45.0)print(xiaoming)xiaoming.run()print(xiaoming)xiaoming.eat()print(xiaoming)print('='*50)print(xiaomei)xiaomei.eat()print(xiaomei)xiaomei.run()print(xiaomei)

3.实例2-拜访家具
class HouseItem:def __init__(self,name,area):self.name = nameself.area = areadef __str__(self):return f'{self.name},占地面积是{self.area}平方'class House:def __init__(self,house_type,area):self.house_type = house_typeself.area = areaself.free_area = areaself.item_list = []def __str__(self):return f'房子户型是{self.house_type},占地面积是{self.area}'def add_item(self,item):if self.area >= item.area:self.item_list.append(item.name)self.free_area -= item.areaelse:print('房子空间不足!')bed = HouseItem('席梦思',4.0)chest = HouseItem('衣柜',2.0)table = HouseItem('餐桌',1.5)print(bed)print(chest)print(table)house = House('三室一厅',50)print(house)house.add_item(bed)house.add_item(chest)house.add_item(table)print(house.item_list)print(house.free_area)
4.士兵突击
class Gun:def __init__(self,model):self.model = modelself.bullet_count = 0def add_bullet(self,count):self.bullet_count += countdef shoot(self):if self.bullet_count == 0:print(f'{self.model}子弹不足!')returnprint('biubiubiu')self.bullet_count -= 1class Soldier:def __init__(self,name):self.name = nameself.gun = Nonedef fire(self):if self.gun is None:print(f'{self.name}还没有枪!')returnprint(f'冲啊{self.name}...')self.gun.add_bullet(50)self.gun.shoot()ak47 = Gun('ak47')xushanduo = Soldier('许三多')xushanduo.fire()xushanduo.gun = ak47print(xushanduo.gun)xushanduo.fire()

7.私有属性和私有方法
1.私有属性
class Women:def __init__(self,name):self.name = nameself.__age = 18def secret(self):print(f'我的名字是{self.name},年龄是{self.__age}')xiaomei = Women('小美')xiaomei.secret()print(xiaomei.__age)

- 程序运行后发现报了一个错误, ‘Women’ object has no attribute ‘__age’,说明age属性不能直接被访问,而可以通过类中方法访问。
2.私有方法
class Women:def __init__(self,name):self.name = nameself.__age = 18def __secret(self):print(f'我的名字是{self.name},年龄是{self.__age}')xiaomei = Women('小美')xiaomei.secret()
- 上面代码中,我将之前的secret方法名的前面加上了两个下划线,这个方法就变成了私有方法,只有在类中可以调用,而想在类的外面调用就会报错:

3.伪私有属性和伪私有方法
class Women:def __init__(self,name):self.name = nameself.__age = 18def __secret(self):print(f'我的名字是{self.name},年龄是{self.__age}')xiaomei = Women('小美')xiaomei._Women__secret()print(xiaomei._Women__age)

- 当定义一个私有属性时,如age,python会将它转换为_Womenage,所有我们可以使用xiaomei._Women_age来访问私有属性。同理,私有方法也是一样。
8.继承
1.单继承
class Animal:def eat(self):print('吃')def drink(self):print('喝')def run(self):print('跑')def sleep(self):print('睡')class Dog(Animal):def bark(self):print('汪汪叫')wangcai = Dog()wangcai.eat()wangcai.drink()wangcai.run()wangcai.sleep()wangcai.bark()
- 上面的代码中,我们并没有在Dog类中定义eat、drink、run、sleep,但却可以调用,这是因为Dog类继承了Animal的方法。

3.继承的传递性
class Animal:def eat(self):print('吃')def drink(self):print('喝')def run(self):print('跑')def sleep(self):print('睡')class Dog(Animal):def bark(self):print('汪汪叫')class XiaoTianQuan(Dog):def fly(self):print('我会飞')wangcai = XiaoTianQuan()wangcai.eat()wangcai.drink()wangcai.run()wangcai.sleep()wangcai.bark()wangcai.fly()
- 上面的程序中Dog类继承了Animal类,XiaoTianQuan类继承了Dog类,这样XiaoTianQuan类就拥有了Animal类和Dog类的所有属性和方法。

4.方法的重写
class Animal:def eat(self):print('吃')def drink(self):print('喝')def run(self):print('跑')def sleep(self):print('睡')class Dog(Animal):def bark(self):print('汪汪叫')class XiaoTianQuan(Dog):def fly(self):print('我会飞')def bark(self):print('叫得跟普通狗不一样')xtq = XiaoTianQuan()xtq.bark()
- 虽然父类中已经定义了bark方法,但是在XiaoTianQuan类中又重新定义了一个bark方法,当对象调用法法时,会调用自身类的方法。

5.扩展父类方法,super()对象调用父类方法
class Animal:def eat(self):print('吃')def drink(self):print('喝')def run(self):print('跑')def sleep(self):print('睡')class Dog(Animal):def bark(self):print('汪汪叫')class XiaoTianQuan(Dog):def fly(self):print('我会飞')def bark(self):print('叫得跟普通狗不一样')super().bark()xtq = XiaoTianQuan()xtq.bark()
- super().bark()调用父类的方法

6.使用父类名调用父类方法
class Animal:def eat(self):print('吃')def drink(self):print('喝')def run(self):print('跑')def sleep(self):print('睡')class Dog(Animal):def bark(self):print('汪汪叫')class XiaoTianQuan(Dog):def fly(self):print('我会飞')def bark(self):print('叫得跟普通狗不一样')Dog.bark(self)xtq = XiaoTianQuan()xtq.bark()
- Dog.bark(self)即使用父类名调用父类方法

8.父类的私有属性和方法
1.父类的私有属性和方法
- 在子类中不能直接调用父类的私有属性和私有方法
class A:def __init__(self):self.num1 = 100self.__num2 = 200def __test(self):print(f'这是A类的私有方法,{self.num1},{self.__num2}')class B(A):def demo(self):print(self.__num2,self.__test())b = B()b.demo()

2.通过父类的共有方法间接访问父类的私有方法和属性
class A:def __init__(self):self.num1 = 100self.__num2 = 200def __test(self):print(f'这是A类的私有方法,{self.num1},{self.__num2}')def test(self):print(f'私有方法:{self.__num2}')self.__test()class B(A):def demo(self):print(f'共有属性:{self.num1}')self.test()b = B()b.demo()
- 可以通过父类的共有方法访问父类属性和调用父类方法

9.多继承
1.多继承
class A:def test(self):print('test方法')class B:def demo(self):print('demo方法')class C(A,B):passc = C()c.test()c.demo()
- 多继承使子类继承所有父类的所有属性和方法。

- 若继承的多个类中的方法有重名的情况,会调用存在同名的继承的类的第一个。
2.MRO方法搜索顺序
class A:def test(self):print('A----test方法')def demo(self):print('A----demo方法')class B:def test(self):print('B----test方法')def demo(self):print('B----demo方法')class C(A,B): # 继承A,B类passc = C()c.test()c.demo()print(C.__mro__)
print(C.mro)打印一个类的MRO

- 输出了一个元组,这个元组代表当对象执行一个属性或者方法时,调用的顺序,先是在自己类中查找,找不到再去第一个父类,接着第二个。
3.super()函数:调用父类的构造方法
使用super()函数,但如果涉及多继承,该函数只能调用第一个直接父类的构造方法。
Super().init会调用第一个父类的构造方法。
那如果要调用多个父类的构造方法该如何实现呢?只需要父类名.init(self)即可实现
4.新式类和经典类
object是Python为所有对象提供的 基类,提供有一些内置的属性和方法,可以使用dir函数查看
新式类:以
object为基类的类,推荐使用经典类:不以
object为基类的类,不推荐使用在
Python 3.x中定义类时,如果没有指定父类,会 默认使用object作为该类的 基类 ——Python 3.x中定义的类都是 新式类在
Python 2.x中定义类时,如果没有指定父类,则不会以object作为 基类
新式类 和 经典类 在多继承时 —— 会影响到方法的搜索顺序
class A(object):passa = A()dir(a)
['__class__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__module__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__','__weakref__']
上面这些都是基类object自带的方法。
10.多态
1.多态的概念
多态*:不同的子类对象条用相同的父类方法,产生不同的执行结果
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
- 是调用方法的技巧,不会影响到类的内部设计
2.实例
class Dog:def __init__(self,name):self.name = namedef game(self):print(f'{self.name}蹦蹦跳跳的玩耍!')class XiaoTianQuan(Dog):def game(self):print(f'{self.name}飞到天上去玩耍!')class Person:def __init__(self,name):self.name = namedef geme_with_dog(self,dog):print(f'{self.name}和{dog.name}快乐的玩耍!')dog.game()wangcai = Dog('旺财')wangcai1 = XiaoTianQuan('飞天旺财')xiaoming = Person('小明')xiaoming.geme_with_dog(wangcai)print('='*50)xiaoming.geme_with_dog(wangcai1)
- 可以看到针对传入不同的对象输出的结果是不一样的。

11.类属性
1.类属性的概念
Python中万物皆对象,类也是一个对象,class AAA()定义的称为类对象
用类创建的对象叫做实例对象。
- 类属性就是给类对象中定义的属性
- 通常用来记录与这个类相关的特性
- 类属性不会用于记录具体对象的特此
class Tool:# 定义类属性count = 0def __init__(self,name):self.name = nameTool.count += 1tool1 = Tool('斧头')tool2 = Tool('榔头')tool3 = Tool('水桶')print(Tool.count)

2.类属性的查找机制
访问类属性可以通过 类名.类属性 的方,在一定的情况下也可以通过 对象名.类属性,程序是如何做到的呢?
当实例对象访问某个属性时,会先查找是否有这个实例属性,如果找不到就会查找类属性是否有这个属性。
class Tool:# 定义类属性count = 0def __init__(self,name):self.name = nameTool.count += 1tool1 = Tool('斧头')tool2 = Tool('榔头')tool3 = Tool('水桶')print(Tool.count)print(tool1.count)

运行结果表示: 属性名.类属性 在一定的情况下是可以实现的。
12.类方法
类方法就是针对类对象定义的方法
在类方法内部可以直接访问类属性或者调用其他的类方法
类方法需要用装饰器修饰
@classmethod来标识,告诉解释器这是一个类方法类方法的第一个参数是cls
- 由哪一个类调用的方法,方法内的
cls就是哪一个类的引用- 这个参数和实例方法的第一个参数是self类似
通过类名.类方法名调用,调用方法时,不需要传递cls参数
在方法内部
- 可以通过cls.访问类的属性
- 也可以通过cls.调用其他的类方法
- 语法:
@classmethoddef 类方法名(cls):pass
- 实例:
class Tool:# 定义类属性count = 0def __init__(self,name):self.name = nameTool.count += 1@classmethoddef show_tool_count(cls):print('工具对象的数量:',cls.count)tool1 = Tool('斧头')tool2 = Tool('榔头')tool3 = Tool('水桶')Tool.show_tool_count()

13.静态方法
静态方法
- 既不需要访问实例属性或者调用实例方法
- 也不需要访问类属性或者调用类方法
- 语法:
@staticmethoddef 静态方法名():pass
- 实例:
class Tool:# 定义类属性count = 0def __init__(self,name):self.name = nameTool.count += 1@classmethoddef show_tool_count(cls):print('工具对象的数量:',cls.count)@staticmethoddef static_method():print('这是一个静态方法')tool1 = Tool('斧头')tool2 = Tool('榔头')tool3 = Tool('水桶')Tool.show_tool_count()tool1.static_method()Tool.static_method()
- 静态方法可以不用创建对象就可以调用,即用类名直接访问。

14.方法综合-案例分析
class Game:top_score = 0 # 创建类属性def __init__(self,player_name): # 构造函数self.player_name = player_name@staticmethod # 创建静态方法def show_help():print('帮助信息:让僵尸进入大门!')@classmethoddef show_top_score(cls): # 创建类方法print('历史最高分为:',cls.top_score)def start_game(self): # 创建实例方法print(f'{self.player_name}准备好,要开始游戏咯')Game.show_help() # 通过类名调用静态方法Game.show_top_score() # 调用类方法user1 = Game('葫芦娃') # 创建实例对象user1.start_game() # 调用实例方法

15.new方法
- 使用类名()创建对象时,Python解释器首先会调用new方法为对象分配空间。
new是一个有object基类提供的内置的静态方法。
- 在内存中为对象分配空间
- 返回对象的引用
Python的解释器获得对象的引用后,将引用作为第一个参数,传递给init方法。
重写new方法的代码非常固定。
- 重写new方法一定要 return super().new(cls)
- 否则Python的解释器得不到分配空间的对象引用,就不会调用初始化方法。
- 注意:new是一个静态方法,在调用时需要主动传递cls参数
class MusicPlayer:def __new__(cls, *args, **kwargs):print('这是new方法')return super().__new__(cls)def __init__(self):print('这是初始化方法!')mp3 = MusicPlayer()print(mp3)

new方法重写格式如下:
class Person(object):def __new__(cls):return object.__new__(cls)
new至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供;new必须要有返回值,返回实例化出来的实例,可以return父类new出来的实例,或直接是object的new出来的实例。
object.new(cls)执行完返回的结果为Person类的实例对象
class Person(object):def __init__(self):print("__init__")self.name="张三"def __new__(cls):print('__new__')ob = object.__new__(cls)#ob为Person实例对象print(ob)return obp1 = Person()print(p1.name)

p1=Person()该语句主要做了以下工作:
首先调用Person的new方法,该方法通过object.new(cls)创建了Person实例对象,并返回。最后调用了该Person实例对象的init方法。object.new()方法接收的参数是类对象,可以不是当前类对象cls,如果将cls换成其他类对象会发生什么呢,看下面代码的运行结果:
class Dog(object):def __init__(self):self.name="旺财"print("Dog.__init__")class Person(object):def __init__(self):self.name="张三"print("Person.__init__")def __new__(cls):print('__new__')ob = object.__new__(Dog)return obp1 = Person()print(type(p1))

- 由结果得知p1是Dog类的实例,但是有个问题,Python解释器没有自动执行init方法,由结果可以看出并没有打印字符串init。若new()没有正确返回当前类cls的实例,那init()将不会被调用。
16.单例设计模式
单例 - - 让类创建的对象,在系统中只有唯一一个实例
- 定义一个类属性,初始值是None,用于记录单例对象的引用
- 重写new方法
- 如果类属性is none ,调用父类方法分配空间,并在类属性中记录结果
- 返回类属性中记录的对象引用
class MusicPlayer:instance = Nonedef __new__(cls, *args, **kwargs):if cls.instance is None:cls.instance = super().__new__(cls)return cls.instancedef __init__(self,name):self.name = namem1 = MusicPlayer('m1')print(m1)print(m1.name)m2 = MusicPlayer('m2')print(m2)print(m2.name)

- 根据运行结果可以得出,对象的内存地址都是一样的,并没有分配多余空间。
- 但是初始化方法执行了多次。
若想让初始化方法只执行一次:
class MusicPlayer:instance = Noneinit_flag = Falsedef __new__(cls, *args, **kwargs):if cls.instance is None:cls.instance = super().__new__(cls)return cls.instancedef __init__(self,name):if not MusicPlayer.init_flag:print('初始化播放器')MusicPlayer.init_flag = Truem1 = MusicPlayer('m1')print(m1)m2 = MusicPlayer('m2')print(m2)

- 只需在类中再创建一个类属性,在初始化方法中根据这个类属性的值判断是否执行。
