目录
一、装饰器补充内容
装饰器带参数
我们先写一个带参数的装饰器,再对其进行解释
def func_arg(name):
def func_out(func):
def wrapped_func():
print('这是一个装饰器')
print('正在执行被装饰的函数...')
func()
print('正在输出装饰器的参数...')
print(name)
return wrapped_func
return func_out
@func_arg('world')
def f():
print('hello')
f()
'''
这是一个装饰器
正在执行被装饰的函数...
hello
正在输出装饰器的参数...
world
'''
1.执行@func_arg('world'),调用func_arg函数,并将‘world’作为参数传递给name。调用后进入func_arg内部进行从上到下执行程序。
2.执行func_arg内部的的def func_out(func):,定义了func_outer函数,将func_out函数加载到内存中
3.执行func_arg内部的return func_out,将func_out函数的引用返回给func_arg函数的调用者:@func_arg('world'),所以此时代码的逻辑变成了
def func_out(func):
def wrapped_func():
print('这是一个装饰器')
print('正在执行被装饰的函数...')
func()
print('正在输出装饰器的参数...')
print(name) # name的值已经在内存中有了
return wrapped_func
@func_out
def f():
print('hello')
f()
到这里就是我们熟悉的正常装饰器了
二、面向对象
1.面向对象和面向过程
面向过程:
面向过程不同于面向对象,面向过程分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象:
面向对象就是构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
区别:
简单来说:用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,就是在米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。
蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
盖浇饭的好处就是“菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是“可维护性”比较好,“饭” 和“菜”的耦合度比较低。蛋炒饭将“蛋”“饭”搅和在一起,想换“蛋”“饭”中任何一种都很困难,耦合度很高,以至于“可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
2.类与对象
类:具有相同特征(在编程中是:属性)和行为(在编程中是:方法)的东西
类通俗理解可以是制造飞机的图纸和模板(不可以直接使用)
对象:由类创建出来的具体存在的东西(可以直接使用),由哪一类创建就具有哪一类的属性和方法
当我们拿到一个需求时,首先要分析需要创建哪些类:比如开发一个植物大战僵尸游戏,最主要的类有:植物类和僵尸类,然后分析每一类的属性(即这一类具有什么相同的特征)和方法(即这一类具有什么相同的行为)
注意:类的命名要满足大驼峰命名法:每一个单词的首字母都要大写
3.面向对象的基础语法
(1)创建类(只包含方法)
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
self是必须参数,self代表对象本身
(2)创建对象
对象名 = 类名()
例:小猫叫爱吃鱼,小猫在喝水
class Cat:
def eat(self):
print('小猫在吃鱼')
def drink(self):
print('小猫在喝水')
little_cat = Cat()
little_cat.eat() # 小猫在吃鱼
little_cat.drink() # 小猫在喝水
如果这只小猫有名字,叫Tom,俨然他的名字就是一个属性,此时我们可以修改代码
class Cat:
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
little_cat = Cat()
little_cat.name = 'Tom'
little_cat.eat() # Tom在吃鱼
little_cat.drink() # Tom在喝水
上面这种是在类的外面给对象添加属性,如果将代码修改成下面这样,他就会报错,所以在类的外面添加属性不推荐
class Cat:
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
little_cat = Cat()
little_cat.eat()
little_cat.drink()
little_cat.name = 'Tom'
(3)初始化方法介绍
class Cat:
def __init__(self):
print('这是一个初始化方法')
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
little_cat = Cat() # 这是一个初始化方法
当创建对象时会自动调用初始化方法:__init__方法
所以我们可以通过给初始化方法传递参数去给类添加属性,这样在创建对象时会自动初始化他的属性
class Cat:
def __init__(self,name):
# 对象的属性名=参数名
self.name = name
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
little_cat = Cat('Tom')
little_cat.eat() # Tom在吃鱼
little_cat.drink() # Tom在喝水
big_cat = Cat('Jerry')
big_cat.eat() # Jerry在吃鱼
big_cat.drink() # Jerry在喝水
(4)__del__方法介绍
对象在从内从地址中销毁之前会自动调用此方法(可以理解为:临死前做的最后一点贡献)
class Cat:
def __init__(self, name):
self.name = name
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
def __del__(self):
print(f'{self.name}跑了')
little_cat = Cat('Tom')
little_cat.eat() # Tom在吃鱼
little_cat.drink() # Tom在喝水
big_cat = Cat('Jerry')
big_cat.eat() # Jerry在吃鱼
big_cat.drink() # Jerry在喝水
print('*' * 50)
'''
Tom在吃鱼
Tom在喝水
Jerry在吃鱼
Jerry在喝水
**************************************************
Tom跑了
Jerry跑了
'''
我们发现当程序执行完时,big_cat和little_cat被自动销毁,并且执行了__del__方法
我们也可以进行手动删除
class Cat:
def __init__(self, name):
self.name = name
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
def __del__(self):
print(f'{self.name}跑了')
little_cat = Cat('Tom')
little_cat.eat() # Tom在吃鱼
little_cat.drink() # Tom在喝水
big_cat = Cat('Jerry')
big_cat.eat() # Jerry在吃鱼
big_cat.drink() # Jerry在喝水
del little_cat
print('*' * 50)
'''
Tom在吃鱼
Tom在喝水
Jerry在吃鱼
Jerry在喝水
Tom跑了
**************************************************
Jerry跑了
'''
我们在打印*前手动删除了little_cat变量,他就执行了__del__方法,big_cat在程序执行完被自动销毁,执行__del__方法
(5)__str__方法
class Cat:
def __init__(self, name):
self.name = name
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
def __del__(self):
print(f'{self.name}跑了')
little_cat = Cat('Tom')
print(little_cat) # <__main__.Cat object at 0x000001832B989190>
little_cat.eat() # Tom在吃鱼
little_cat.drink() # Tom在喝水
我们在加入__str__方法之前,打印对象的变量名,会发现输出的是对象在内存中的地址
class Cat:
def __init__(self, name):
self.name = name
def __str__(self):
return f'这只猫的名字叫{self.name}'
def eat(self):
print(f'{self.name}在吃鱼')
def drink(self):
print(f'{self.name}在喝水')
def __del__(self):
print(f'{self.name}跑了')
little_cat = Cat('Tom')
print(little_cat) # 这只猫的名字叫Tom
little_cat.eat() # Tom在吃鱼
little_cat.drink() # Tom在喝水
当我们设置了__str__方法中return的内容后,再打印对象的变量名,输出就是__str__方法中return的内容
4.面向对象的封装
需求:小明体重75公斤,小明每次跑步会减肥0.5公斤,小明每次吃东西体重会增加1公斤
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):
# 调用一次,体重减0.5
print('跑步...')
self.weight -= 0.5
def eat(self):
# 调用一次,体重增加1
print('吃...')
self.weight += 1
def __del__(self):
print(f'{self.name}现在的体重是{self.weight}')
xiaoming = Person('小明', 75)
xiaoming.run() # 跑步...
xiaoming.run() # 跑步...
xiaoming.eat() # 吃
print(xiaoming) # 我是小明,我的体重是75.0
xiaoming.run() # 跑步...
xiaoming.eat() # 吃
xiaoming.eat() # 吃
xiaoming.eat() # 吃
print('*' * 50)
# 小明现在的体重是77.5
需求:房子(House)有户型,总面积和家具名称列表,新房子中没有任何的家具,家具(HouseItem)有家具名字和占地面积,其中席梦思(bed)占地4平米,衣柜(chest)占地2平米,餐桌(table)占地2.5平米,将以上三件家具添加到房子中,打印房子时,要求输出户型,总面积,剩余面积,家具名称列表
分析:
House类:属性:面积,户型,家具名称列表,剩余面积 方法:初始化方法,str方法,添加家具方法
HouseItem类:属性:名称,面积 方法:初始化方法,str方法
因为House类中要用到家具,所以先开发HouseItem类
class HouseItem:
def __init__(self, name, area):
"""
:param name: 家具名称
:param area: 占地面积
"""
self.name = name
self.area = area
def __str__(self):
return '[%s] 占地面积是 %.2f 平米' % (self.name, self.area)
# 创建家具
bed = HouseItem('席梦思', 4)
chest = HouseItem('衣柜', 2)
table = HouseItem('餐桌', 2.5)
print(bed)
print(chest)
print(table)
class House:
def __init__(self, house_type, area):
"""
:param house_type: 户型
:param area: 总面积
"""
self.house_type = house_type
self.area = area
# 剩余面积默认和总面积保持一致
self.free_area = area
# 家具名称列表(默认没有任何家具)
self.item_list = []
def __str__(self):
return ("户型:%s\n总面积:%.2f平米[剩余面积:%.2f平米]\n家具名称:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print("要添加%s" % item)
# 1. 判断家具的面积是否大于剩余面积
if item.area > self.free_area:
print("要添加的%s面积过大,不能添加" % item.name)
return
# 2. 将家具的名称添加到列表中
self.item_list.append(item.name)
# 3. 计算剩余面积
self.free_area -= item.area
my_home = House("两室一厅", 60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
5.面向对象的私有属性和私有方法
对象 的 某些属性或方法 只希望 在对象的内部被使用,而 不希望在外部被访问到
定义:在属性名或者在方法名前面加两个下划线
class Girl:
def __init__(self, name, age):
self.name = name
# 定义私有属性
self.__age = age
# 定义私有方法
def __secret(self):
print(f'{self.name}的年龄是{self.__age}')
xiaomei = Girl('小美', 20)
print(xiaomei.name) # 小美
print(xiaomei.__age) # 报错
xiaomei = Girl('小美', 20)
xiaomei.__secret() # 报错
我们可以看到输出name属性可以正常输出,但我们明明定义了__age属性,却在输出时报错
class Girl:
def __init__(self, name, age):
self.name = name
# 定义私有属性
self.__age = age
# 定义私有方法
def __secret(self):
print(f'{self.name}的年龄是{self.__age}')
xiaomei = Girl('小美', 20)
xiaomei.__secret() # 报错
同样,在调用私有方式时也会报错
然而,在python中并没有真正的私有,我们还是有办法访问到私有属性和私有方法
class Girl:
def __init__(self, name, age):
self.name = name
# 定义私有属性
self.__age = age
# 定义私有方法
def __secret(self):
print(f'{self.name}的年龄是{self.__age}')
xiaomei = Girl('小美', 20)
print(xiaomei._Girl__age) # 20
我们在调用__age属性前加上 _类名,就可以使用私有属性
class Girl:
def __init__(self, name, age):
self.name = name
# 定义私有属性
self.__age = age
# 定义私有方法
def __secret(self):
print(f'{self.name}的年龄是{self.__age}')
xiaomei = Girl('小美', 20)
xiaomei._Girl__secret() # 小美的年龄是20
我们在调用__secret方法前加上 _类名,就可以调用私有方法
6.面向对象的继承
继承:子类拥有父类的所有属性和方法
class Animal:
def eat(self):
print('eat')
def sleep(self):
print('sleep')
class Dog:
def eat(self):
print('eat')
def sleep(self):
print('sleep')
dog = Dog()
dog.eat() # eat
dog.sleep() # sleep
狗属于动物,动物类可以吃和睡,狗类也可以吃和睡,狗既然属于动物类,那么是否有一种办法不用在狗类里面重新定义一个吃和睡的方法吗(避免代码的冗余)?这就要使用到继承
使用继承开发:
class Animal:
def eat(self):
print('eat')
def sleep(self):
print('sleep')
# 子类(继承的父类)
class Dog(Animal):
# 可以添加子类特有的方法
def bark(self):
print('bark')
dog = Dog()
dog.eat() # eat
dog.sleep() # sleep
dog.bark() # bark
可以看见我们在继承动物类的狗类中并没有定义eat和sleep方法,但可以调用父类(Animal类)中的eat和sleep方法
class Animal:
def eat(self):
print('eat')
def sleep(self):
print('sleep')
# 子类(继承的父类)
class Dog(Animal):
# 可以添加子类特有的方法
def bark(self):
print('bark')
class XiaoTianQuan(Dog):
def fly(self):
print('fly')
dog = XiaoTianQuan()
dog.eat() # eat
dog.sleep() # sleep
dog.bark() # bark
dog.fly() # fly
我们又定义了一个哮天犬类让他继承于狗类,而狗类继承于动物类,我们发现哮天犬类的对象可以调用父类的父类的方法,说明继承具有传递性
方法的重写
当父类中的方法不满足子类的需求时,我们需要怎么办呢?
1.覆盖父类的方法
class Dog(Animal):
# 可以添加子类特有的方法
def bark(self):
print('bark')
class XiaoTianQuan(Dog):
def fly(self):
print('fly')
比如哮天犬的叫和狗类的叫不一样,我们应该怎么办?
class Dog():
# 可以添加子类特有的方法
def bark(self):
print('bark')
class XiaoTianQuan(Dog):
def fly(self):
print('fly')
def bark(self):
print('哮天犬的bark')
dog = XiaoTianQuan()
dog.bark() # 哮天犬的bark
我们可以在哮天犬类中再定义一个bark方法,当执行dog.bark()时,会先找子类的bark方法,找到了就执行子类的bark方法,如果子类没有再去父类找bark方法。这就是方法重写的第一种:覆盖父类的方法
2.扩展父类方法
父类的方法不能完全满足需求(只能满足部分需求),就要在子类中对父类的方法进行扩展,这个如何实现呢?
class Dog():
# 可以添加子类特有的方法
def bark(self):
print('bark')
class XiaoTianQuan(Dog):
def fly(self):
print('fly')
def bark(self):
super().bark()
print('哮天犬的bark')
dog = XiaoTianQuan()
dog.bark()
'''
bark
哮天犬的bark
'''
在子类中也定义一个bark方法,使用 super().父类方法名() 的形式对父类方法名进行调用,然后在增加子类特有的需求,这样最后在创建完对象后,调用bark方法,就会调用子类中拓展完父类bark的bark方法
注意:当父类中有私有方法和私有属性时,子类并不能直接继承父类的私有属性和私有方法,如果想使用就可以使用我们上面介绍的方法。
多继承
一个子类继承于多个父类就叫多继承
class A(object):
def test(self):
print('A---test')
def demo(self):
print('A---demo')
class B(object):
def demo(self):
print("B---demo")
def test(self):
print('B---test')
class C(B, A):
pass
c = C()
c.test() # B---test
c.demo() # B---demo
注意事项:当子类继承的多个父类中有名字相同的方法,并且我们调用了此方法时,会优先执行括号中第一个父类的该方法
7.多态
不同的子类调用相同的父类方法产生不同的结果叫做多态
前提条件:
1.继承:多态一定是发生在子类和父类之间;
2.重写:子类重写了父类的方法。
class WhoSay:
def say(self,who):
who.say()
class CLanguage:
def say(self):
print("调用的是 Clanguage 类的say方法")
class CPython(CLanguage):
def say(self):
print("调用的是 CPython 类的say方法")
class CLinux(CLanguage):
def say(self):
print("调用的是 CLinux 类的say方法")
a = WhoSay()
#调用 CLanguage 类的 say() 方法
a.say(CLanguage())
#调用 CPython 类的 say() 方法
a.say(CPython())
#调用 CLinux 类的 say() 方法
a.say(CLinux())
'''
调用的是 Clanguage 类的say方法
调用的是 CPython 类的say方法
调用的是 CLinux 类的say方法
'''