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 = name
print('这是一个初始化方法!')
# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)
tom = Cat('tom')
print(tom.name)
4.内置方法__del__方法和对象生命周期
- 当一个对象被从内存中销毁前,会调用__del__方法。
__init__
改造初始化方法,可以让创建对象更加灵活__del__
如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__
方法
生命周期
- 一个对象从调用
类名()
创建,生命周期开始 - 一个对象的
__del__
方法一旦被调用,生命周期结束 - 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
class Cat:
def __init__(self,name):
self.name = name
print('这是一个初始化方法!')
def __del__(self):
print('这个对象被销毁了')
# 使用类名()创建对象的时候,会自动调用初始化方法(__init__)
tom = Cat('tom')
print(tom.name)
del tom # del关键字可以删除一个对象
5.内置方法__str__方法定制变量输入信息
class Cat:
def __init__(self,name):
self.name = name
print('这是一个初始化方法!')
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 = name
print('这是一个初始化方法!')
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 = name
self.weight = weight
def __str__(self):
return f'我的名族叫:{self.name},我的体重是:{self.weight}公斤'
def run(self):
print(f'{self.name}爱跑步')
self.weight -= 0.5
def eat(self):
print(f'{self.name}爱跑步')
self.weight += 1
xiaoming = Person('小明',75.0)
print(xiaoming)
xiaoming.run()
print(xiaoming)
xiaoming.eat()
print(xiaoming)
3.多个对象之间互不干扰
class Person:
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __str__(self):
return f'我的名族叫:{self.name},我的体重是:{self.weight}公斤'
def run(self):
print(f'{self.name}爱跑步')
self.weight -= 0.5
def eat(self):
print(f'{self.name}爱跑步')
self.weight += 1
xiaoming = 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 = name
self.area = area
def __str__(self):
return f'{self.name},占地面积是{self.area}平方'
class House:
def __init__(self,house_type,area):
self.house_type = house_type
self.area = area
self.free_area = area
self.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.area
else:
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 = model
self.bullet_count = 0
def add_bullet(self,count):
self.bullet_count += count
def shoot(self):
if self.bullet_count == 0:
print(f'{self.model}子弹不足!')
return
print('biubiubiu')
self.bullet_count -= 1
class Soldier:
def __init__(self,name):
self.name = name
self.gun = None
def fire(self):
if self.gun is None:
print(f'{self.name}还没有枪!')
return
print(f'冲啊{self.name}...')
self.gun.add_bullet(50)
self.gun.shoot()
ak47 = Gun('ak47')
xushanduo = Soldier('许三多')
xushanduo.fire()
xushanduo.gun = ak47
print(xushanduo.gun)
xushanduo.fire()
7.私有属性和私有方法
1.私有属性
class Women:
def __init__(self,name):
self.name = name
self.__age = 18
def 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 = name
self.__age = 18
def __secret(self):
print(f'我的名字是{self.name},年龄是{self.__age}')
xiaomei = Women('小美')
xiaomei.secret()
- 上面代码中,我将之前的secret方法名的前面加上了两个下划线,这个方法就变成了私有方法,只有在类中可以调用,而想在类的外面调用就会报错:
3.伪私有属性和伪私有方法
class Women:
def __init__(self,name):
self.name = name
self.__age = 18
def __secret(self):
print(f'我的名字是{self.name},年龄是{self.__age}')
xiaomei = Women('小美')
xiaomei._Women__secret()
print(xiaomei._Women__age)
- 当定义一个私有属性时,如__age,python会将它转换为_Women__age,所有我们可以使用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 = 100
self.__num2 = 200
def __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 = 100
self.__num2 = 200
def __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):
pass
c = 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类
pass
c = 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):
pass
a = 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 = name
def game(self):
print(f'{self.name}蹦蹦跳跳的玩耍!')
class XiaoTianQuan(Dog):
def game(self):
print(f'{self.name}飞到天上去玩耍!')
class Person:
def __init__(self,name):
self.name = name
def 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 = 0
def __init__(self,name):
self.name = name
Tool.count += 1
tool1 = Tool('斧头')
tool2 = Tool('榔头')
tool3 = Tool('水桶')
print(Tool.count)
2.类属性的查找机制
访问类属性可以通过 类名.类属性 的方,在一定的情况下也可以通过 对象名.类属性,程序是如何做到的呢?
当实例对象访问某个属性时,会先查找是否有这个实例属性,如果找不到就会查找类属性是否有这个属性。
class Tool:
# 定义类属性
count = 0
def __init__(self,name):
self.name = name
Tool.count += 1
tool1 = Tool('斧头')
tool2 = Tool('榔头')
tool3 = Tool('水桶')
print(Tool.count)
print(tool1.count)
运行结果表示: 属性名.类属性 在一定的情况下是可以实现的。
12.类方法
类方法就是针对类对象定义的方法
在类方法内部可以直接访问类属性或者调用其他的类方法
类方法需要用装饰器修饰
@classmethod
来标识,告诉解释器这是一个类方法类方法的第一个参数是cls
- 由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用- 这个参数和实例方法的第一个参数是self类似
通过类名.类方法名调用,调用方法时,不需要传递cls参数
在方法内部
- 可以通过cls.访问类的属性
- 也可以通过cls.调用其他的类方法
- 语法:
@classmethod
def 类方法名(cls):
pass
- 实例:
class Tool:
# 定义类属性
count = 0
def __init__(self,name):
self.name = name
Tool.count += 1
@classmethod
def show_tool_count(cls):
print('工具对象的数量:',cls.count)
tool1 = Tool('斧头')
tool2 = Tool('榔头')
tool3 = Tool('水桶')
Tool.show_tool_count()
13.静态方法
- 静态方法
- 既不需要访问实例属性或者调用实例方法
- 也不需要访问类属性或者调用类方法
- 语法:
@staticmethod
def 静态方法名():
pass
- 实例:
class Tool:
# 定义类属性
count = 0
def __init__(self,name):
self.name = name
Tool.count += 1
@classmethod
def show_tool_count(cls):
print('工具对象的数量:',cls.count)
@staticmethod
def 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('帮助信息:让僵尸进入大门!')
@classmethod
def 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 ob
p1 = 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 ob
p1 = Person()
print(type(p1))
- 由结果得知p1是Dog类的实例,但是有个问题,Python解释器没有自动执行__init__方法,由结果可以看出并没有打印字符串__init__。若__new__()没有正确返回当前类cls的实例,那__init__()将不会被调用。
16.单例设计模式
单例 - - 让类创建的对象,在系统中只有唯一一个实例
- 定义一个类属性,初始值是None,用于记录单例对象的引用
- 重写new方法
- 如果类属性is none ,调用父类方法分配空间,并在类属性中记录结果
- 返回类属性中记录的对象引用
class MusicPlayer:
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self,name):
self.name = name
m1 = MusicPlayer('m1')
print(m1)
print(m1.name)
m2 = MusicPlayer('m2')
print(m2)
print(m2.name)
- 根据运行结果可以得出,对象的内存地址都是一样的,并没有分配多余空间。
- 但是初始化方法执行了多次。
若想让初始化方法只执行一次:
class MusicPlayer:
instance = None
init_flag = False
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self,name):
if not MusicPlayer.init_flag:
print('初始化播放器')
MusicPlayer.init_flag = True
m1 = MusicPlayer('m1')
print(m1)
m2 = MusicPlayer('m2')
print(m2)
- 只需在类中再创建一个类属性,在初始化方法中根据这个类属性的值判断是否执行。