目录
一、装饰器和闭包
1、装饰器 decorator
定义:在不修改目标函数代码的前提下,为目标函数添加新功能的函数。
本质上就是一个函数(或类)
作用:为函数增加新功能,减少重复性操作,使代码简洁
使用场景:性能测试、打印日志等
使用装饰器的步骤:
- 定义装饰器
- 通过@调用装饰器
import time
#定义装饰器
def timer(function):
def wrapper():
time_start=time.time()
function()
time_end=time.time()
cost_time=time_end-time_start
print("花费时间:{}秒".format(cost_time))
return wrapper
#通过@调用装饰器
@timer
def func1():
print('我是一个函数1')
@timer
def func2():
print('我是一个函数2')
func1()
func2()
# 我是一个函数1
# 花费时间:0.0秒
# 我是一个函数2
# 花费时间:0.0秒
2、闭包 closure
定义:在一个内部函数里,对外部作用域(但不是全局作用域)里变量进行引用,这个内部函数就是闭包
def f():
a=1
def g():
nonlocal a#只访问不更改不需要nonlocal,更改需要添加nonlocal
a += 1
print(a)
return g
g1=f()
g2=f()
g1()#2
g1()#3
g1()#4
g2()#2
def f():
a=[[],[]]
def g():
a[0].append(111)
print(a)
print(id(a[0]),id(a[1]))
return g
g1=f()
g2=f()
g1()
g1()
g2()
#[[111], []]
#2043067833216 2043066699392
#[[111, 111], []]
#2043067833216 2043066699392
#[[111], []]
#2043067833664 2043067832704
二、迭代器与生成器
迭代:一种操作,逐个获取数据的过程称为迭代,如for循环
可迭代对象:()、[ ]、{ }、文件对象都是可以迭代的
迭代器 iterator:它包含一组元素,每次调用会返回自身的下一个元素,一个迭代器对象必须是定义了_iter()_方法和next()方法的
- 所有可迭代对象都可以用 lter() 函数转变为迭代器
- 迭代器对象可以用next() 不断返回下一个数据,但是当所有元素都被返回时,再执行next(),程序会报错--stoplteration
- 不能提前知道序列长度,即不能使用len()计算序列长度
生成器 generator :是特殊的迭代器
定义:在Python中,使用了yield的函数 就是生成器
在调用生成器运行的过程中,每次遇到yield时,函数会暂停并保存当前所有运行信息,返回yield值,并在下一次执行next()方法时从当前位置继续运行
#简单的生成器示例:利用生成器输出1-10000000000的数
list=(i for i in range(1,10000000001))
for item in list:
print(item)
注意:这里是(),而不是[ ],如果使用列表,内存会爆炸
# 生成器
def gen(num):
while num>0:
yield num
num -= 1
return
g=gen(5)#调用生成器函数,返回一个生成器对象到g里
first=next(g)#使用next函数时才开始真正运行他的函数本体,此时first=5
for i in g: #每次都call了个next
print(i)
#4
#3
#2
#1
迭代器和生成器的区别
从使用者角度,生成器和迭代器是没什么区别的,使用方法几乎一样
生成器有send用法,在生成器yield之后,把yield的东西变成一个值,这个值还可以继续赋给生成器函数中的其他变量
#send用法
def gen(num):
while num>0:
tmp=yield num #将yield的值赋给tmp
if tmp is not None:
num=tmp
num-=1
g=gen(5)
first=next(g) #first=g.send(None)
print(f"first:{first}")
print(f"send:{g.send(10)}")#10赋值给tmp,num=tmp=10,next()出10
for i in g:
print(i)
# first:5
# send:9
# 8
# 7
# 6
# 5
# 4
# 3
# 2
# 1
从背后原理,迭代器是将迭代时的状态保存到一个对象里的;生成器是将状态保存到frame,它的状态是函数记录到哪一步,而不是通过一个变量记录状态,更为简洁
生成器的代码量比传统的迭代器少很多
二、面向对象编程
oop三大特征:继承、多态、封装
1、概念
面向过程:一开始学习的,按照解决问题的步骤编程【根据业务逻辑从上到下编程】
函数式:将某功能代码封装到函数中,下次使用直接调用,无需重复编写
面向对象编程:将数据与函数绑在一起封装,这样能够更快速的开发程序,减少重复代码的重写过程 oop(object oriented programming),是一种解决软件复用的设计和编程方法,这种方法将软件系统中相近相似的操作逻辑和操作应用数据、状态,以类的形式描述出来,以对象实例的形式在软件系统中复用,以达到提高软件开发效率的作用
面向过程适合做小项目,面向对象适合做大项目
2、类和对象
2.1 概念
类:一个模板,模板里包含多个函数,函数中实现一些功能(汽车图纸、车类)
是一组具有相同或相似属性和行为的多个对象的组合
对象:类的实例化,可以执行类中的函数(宝马)
2.2 定义类和创建对象
定义类:类结构=类名(首字母大写)+属性+方法行为
实例方法:在类内部,用def可以定义实例方法,与一般函数不同的是实例方法必须包含参数,默认第一个参数是self(名字标识可以是其他名字,但这个位置必须被占用)
# class 类名:
# 属性
# 方法
class Person:#类名:首字母大写
#属性
name='小明'#类属性
age=20
#方法(行为) 实例方法
def eat(parms):
parms.name='小红'#实例属性
print('eating')
pass
def run(self):
print('running')
pass
pass
创建对象:
格式:对象名=类名()
#创建对象[类的实例化]
xm=Person()
#调用函数
xm.eat()
print('{}的年龄是:{}岁'.format(xm.name,xm.age))
属性:
- 类属性:类对象所拥有的属性(定义在类内部,实例方法外)
- 实例属性:实例对象拥有的属性,只能通过实例对象访问(实例方法内定义的【通过类似于self.变量名】变量)
class Student:
name='李明'#类属性
def __init__(self,age):
self.age=age#实例属性
pass
pass
lm=Student(18)
print(Student.name)
print(lm.name)#类属性可以被类对象和实例对象访问
print(lm.age)#实例属性只能通过实例对象访问
print('不能通过类对象访问实例属性')
print(Student.age)
# 李明
# 李明
# 18
# 不能通过类对象访问实例属性
# Traceback (most recent call last):
# File "E:\资源下载\workspace\shixun\pythonProject2\2.17多态.py", line 47, in <module>
# print(Student.age)
# AttributeError: type object 'Student' has no attribute 'age'
类属性和实例属性的访问原理:
2.3 __init__(self):
Python自带的内置函数
是一个初始化方法,用来定义实例属性和初始化数据,在创建对象时自动调用
class Person:
def __init__(self):
self.name='小倩'
self.age=20
def run(self):
print('running')
pass
pass
xq=Person()#创建新对象时,__init__自动调用
print(xq.name)
#小倩
传递参数(后面实例方法都可以直接使用该参数)
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
def eat(self,food):
print(self.name+'喜欢吃'+food)
xm=Person('小明',21)
xm.eat('苹果')
2.4 self的理解
self和对象指向同一内存地址,self是对象的引用,可以理解为对象自己
class Person:
def eat(self):
print('self=%s',id(self))
pass
pass
xm=Person()
xm.eat()
print('xm=%s',id(xm))
#self=%s 1760559500112
#xm=%s 1760559500112
- self只有在类中定义实例方法时才有意义,在调用时不必传入相应参数,而是由解释权自动指向
- self的名称可以更改,只是约定俗成的定义成了self
- self指的是类实例对象本身
self传参:
class Person:
def __init__(self,pro):
self.pro=pro
def eat(self,name,food,pro):
print('%s喜欢吃%s,修的专业是%s,%s'%(name,food,self.pro,pro))
pass
pass
xm=Person('通信工程')
xm.eat('小明','榴莲','hhh')
# 小明喜欢吃榴莲,修的专业是通信工程,hhh
3、魔术方法
定义:Python中的一些内置好的特定方法,方法名为“__xxx__”,前后有两个下划线
- 在进行特定操作时,魔术方法会自动调用
常见的魔术方法:
3.1__str__()
定义了__str__方法,在打印对象时,会执行__str__方法(__str__只能return一个字符串)
class Animal:
def __init__(self,name,colour):
self.name=name
self.colour=colour
def __str__(self):
return '我的名字是%s,我的颜色是%s'%(self.name,self.colour)
dog=Animal('旺财','白色')
print(dog)
#我的名字是旺财,我的颜色是白色
#改变打印对象的内容
class Animal:
def __init__(self,name,colour):
self.name=name
self.colour=colour
dog=Animal('旺财','白色')
print(dog)
# <__main__.Animal object at 0x000001E49FEF6FD0>
#直接输出对象的地址
3.2 __new__()
调用对象实例的方法,每调用一次生成一个新对象,cls是class的缩写
class Animal(object):
def __init__(self,name,colour):
self.name=name
self.colour=colour
print('__init__函数执行')
def __str__(self):
return '我的名字是%s,我的颜色是%s'%(self.name,self.colour)
def __new__(cls, *args, **kwargs):
print('__new__函数执行')
# return object.__new__(cls)#这里真正创建对象实例
dog=Animal('旺财','白色')
print(dog)
#__new__函数执行
#None
#解释:还没有生成对象实例
class Animal(object):
def __init__(self,name,colour):
self.name=name
self.colour=colour
print('__init__函数执行')
def __str__(self):
return '我的名字是%s,我的颜色是%s'%(self.name,self.colour)
def __new__(cls, *args, **kwargs):
print('__new__函数执行')
return object.__new__(cls)#这里真正创建对象实例
# return super().__new__(cls)#若没有父类object,用super()创建对象实例
dog=Animal('旺财','白色')
print(dog)
# __new__函数执行
# __init__函数执行
# 我的名字是旺财,我的颜色是白色
#先执行__new__(),创建对象实例后执行__init__
#__new__()创建实例框架,__init__丰满
__new__方法在__init__方法前执行
__new__方法不能调用自己的__new__方法,即:return cls.__new__(cls)
- 否则会报错:RecursionError:maximum recursion depth exceeded超过最大递归深度
__new__和__init__的区别
- __new__:类的实例化方法,必须返回该实例,不然对象创建不成功
- __init__:做对象的初始化工作,也可以认为是实例的构造方法,接受该实例self并对其构造
- __new__至少一个参数是cls,代表要实例化的类,该参数在实例化时由Python解释器自动提供
- __new__要早于__init__函数执行
课后问答
4、析构方法
当一个对象被删除或销毁时,Python解释器默认调用__del__()方法,这个方法也称析构方法,也是一种魔术方法
- 用于对象的释放,对象一旦被释放,不能再使用
程序执行结束自动调用__del__()
class Animal:
def __init__(self,name):
self.name=name
print('__init__执行')
def __del__(self):
print('__del__执行')
pass
cat=Animal('cat')
print('程序等待中。。。')
# __init__执行
# 程序等待中。。。
# __del__执行
对象被删除时,也会自动调用__del__方法
class Animal:
def __init__(self,name):
self.name=name
print('__init__执行')
def __del__(self):
print('__del__执行')
pass
cat=Animal('cat')
del cat#手动去清理对象
print('*'*20)
# __init__执行
# __del__执行
# ********************
5、类的继承*
Python面向对象的三大特征:封装、继承、多态
封装:把内容封装到某个地方,便于后面的使用【其实就是使用初始化构造方法将内容封装到对象中,然后通过对象直接或者self来获取封装的内容】
继承:即子可以继承父的内容(属性和行为)
- 将多个类共有的方法提取到【父类】中,然后通过继承,极大提高了效率,减少代码的重复编写
5.1 单继承
class Animal:
def eat(self):
print('eating')
class Cat(Animal):
pass
cat1=Cat()
cat1.eat()
#eating
5.2 多继承
子类继承多个父类,用逗号分隔 class C(A,B)
问题是:当多个父类中存在相同方法时,应该调用哪一个呢?
class E:
def eat(self):
print('E.eat')
class D:
def eat(self):
print('D.eat')
class C(E):
# def eat(self):
# print('C.eat')
pass
class B(D):
pass
class A(B,C):
pass
a1=A()
a1.eat()#查找顺序,A中没有,找B,B中没有,找C
print(A.__mro__)
# D.eat
# (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)
__mro__方法:查询执行顺序
继承的传递性:爸爸继承了爷爷,儿子继承爸爸,也继承了爷爷
5.3 重写和调用父类方法
子类中有个跟父类方法名称一样的方法,相当于重写父类方法(方法覆盖)
重写父类方法后,子类调用父类方法时,调用的是子类的方法
class Father:
def eat(self):
print('爸爸')
pass
class Son(Father):
def eat(self):
print('儿子')
pass
son=Son()
son.eat()
print(Son.__mro__)
# 儿子
# (<class '__main__.Son'>, <class '__main__.Father'>, <class 'object'>)
调用父类方法
class Dog:
def __init__(self,name,color):
self.name=name
self.color=color
def bark(self):
print('汪汪汪。。')
class Keji(Dog):
def __init__(self,name,color,age):#重写父类方法
# Dog.__init__(self,name,color)#调用父类方法1--手动调用
super().__init__(name,color)#调用父类方法2--自动调用:super()自动查找父类方,进而调用方法
self.age=age
def dark(self):
print('啊啊啊。。')
super().bark()#调用父类方法
print('啊啊啊。。')
def msg(self):
print('名字:%s,颜色:%s,年龄:%s'%(self.name,self.color,self.age))
keji=Keji('柯基','黄白',2)
keji.dark()
keji.msg()
# 啊啊啊。。
# 汪汪汪。。
# 啊啊啊。。
# 名字:柯基,颜色:黄白,年龄:2
super():假如继承了多个父类,则会按顺序查找
class Animal:
def bark(self):
print('动物叫。。')
class Keji(Animal,Dog):
def __init__(self,name,color,age):#重写父类方法
# Dog.__init__(self,name,color)#调用父类方法1
super().__init__(name,color)#调用父类方法2:super()自动查找父类方,进而调用方法
self.age=age
def dark(self):
print('啊啊啊。。')
super().bark()#调用父类方法
print('啊啊啊。。')
def msg(self):
print('名字:%s,颜色:%s,年龄:%s'%(self.name,self.color,self.age))
keji=Keji('柯基','黄白',2)
keji.dark()
keji.msg()
# 啊啊啊。。
# 动物叫。。
# 啊啊啊。。
# 名字:柯基,颜色:黄白,年龄:2
6、多态
定义:就是多种状态(形态),就是同一种行为,对于不同子类【对象】有不同的行为表现
想实现多态必须满足的两个条件:
- 继承:多态必须发生在父态和子态之间
- 重写:子类重写父类方法
作用:增加程序的灵活性和扩展性
鸭子类型:在程序设计中,鸭子类型是动态类型的一种风格。“鸭子测试”可以表述为:“当一个鸟走起来像鸭子,叫起来像鸭子,那么这只鸟可以称为鸭子”
class Animal:
def say(self):
print("动物叫")
pass
class Dog(Animal):
def say(self):
print('汪汪汪')
pass
class Cat(Animal):
def say(self):
print('喵喵喵')
pass
# dog=Dog()
# dog.say()
# cat=Cat()
# cat.say()
def common(obj):
obj.say()
i=[Dog(),Cat()]
for item in i:
common(item)
# 汪汪汪
# 喵喵喵
7、类方法和静态方法
类方法:类对象所拥有的方法,用装饰器@classmate标识,类方法的第一个参数必须是类对象,一般以cls作为第一个参数。
可以通过类对象、实例对象调用
class Student:
name='李华'#类属性
@classmethod
def get_name(cls):
return cls.name#访问类属性
@classmethod
def change_name(cls,new_name):
cls.name=new_name#在类方法中修改类属性的值
print(Student.get_name())#类对象引用
lh=Student()
print(lh.get_name())#实例对象引用
# 李华
# 李华
Student.change_name("小花")
print(Student.get_name())#类对象引用
xh=Student()
print(xh.get_name())#实例对象引用
# 小花
# 小花
静态方法:类对象所拥有的方法,需要用@staticmethod来表示静态方法
class Student:
name='李华'#类属性
@staticmethod
def getdate():
return Student.name
print(Student.getdate())#类对象引用
lh=Student()
print(lh.getdate())#实例对象引用
# 李华
# 李华
静态方法一般不会通过实例对象访问
为什么使用静态方法?
静态方法主要用来存放逻辑性代码,在静态方法中,不会涉及到类中方法和属性的操作,数据资源能得到有效利用
#引入第三方时间模块
import time
class Timetest:
@staticmethod
def showtime():
return time.strftime("%H:%M:%S",time.localtime())
pass
print(Timetest.showtime())
# 15:46:56
- 类方法的第一个参数是类对象cls,进而去引用类对象的属性和方法
- 实例方法的第一个参数必须是self,通过self去引用类属性或实例属性,若存在相同名称实例属性和类属性,实例属性优先级最高
- 静态方法不需要定义额外参数,需要引用属性时,可通过类对象或实例对象去引用
8、私有化属性和方法--保护和控制数据
8.1私有化属性
保护属性安全,不得随意修改,将属性定义成私有化属性,添加可调用的方法去访问
使用私有化属性的场景:
- 把特定属性隐藏,不想让类的外部直接调用(不能在外部直接访问,在类的内部可以随意访问)
- 保护这个属性,不想让该属性的值随意改变(内部可以修改)
- 保护属性,不想让派生类【子类】去继承(子类不能继承父类的私有属性)
语法:两个下划线开头,声明属性为私有属性,不能在类的外部被使用或直接访问
class Person:
def __init__(self):
self.name='李四'
self.__age=18#属性私有化(外部不能访问,内部可以访问)
pass
def __str__(self):
return('{}的年龄是{}'.format(self.name,self.__age))
x1=Person()
print(x1)
print(x1.name)
print(x1.__age)#通过类对象在外部访问(私有化后,不能在外部直接访问)
# Traceback (most recent call last):
# File "E:\资源下载\workspace\shixun\pythonProject2\2.17多态.py", line 125, in <module>
# print(x1.__age)#通过类对象在外部访问(私有化后,不能在外部直接访问)
# AttributeError: 'Person' object has no attribute '__age'
# 李四的年龄是18
# 李四
8.2 私有化方法
语法:方法名前加两个下划线
- 私有化的方法不能被【子类】继承
- 外部不能直接调用,内部可以调用
__xxx:方法私有化
__xxx__:魔术方法,Python自有,开发者不要创建
xxx_:避免属性名与Python关键字冲突
9、property属性函数
让调用者能直接以访问属性的方式,而且又能控制的方式
实现方法1-- 类属性
class Animal(object):
def __init__(self):
self.__age=18#私有化属性
def dog(self):#访问私有属性
return self.__age
def chance(self,age):#修改私有实例属性
if age<0:
print('年龄不能小于0')
pass
else:
self.__age=age
#定义一个类属性,实现直接访问属性的形式去访问私有的属性
age=property(dog,chance)
a1=Animal()
print(a1.age)#访问私有属性
#18
a1.age=2#修改私有属性
print(a1.age)
#2
实现方法2:装饰器*
即在方法上使用装饰器
class Animal(object):
def __init__(self):
self.__age=18#私有化属性
@property#通过装饰器修饰添加属性标志,提供一个getter方法
def dog(self):#访问私有属性
return self.__age
@dog.setter#提供一个setter方法
def chance(self,parms):#修改私有实例属性
if parms<0:
print('年龄不能小于0')
pass
else:
self.__age=parms
pass
a1=Animal()
print(a1.dog)#访问私有属性
#18
a1.chance=2#修改私有属性
print(a1.dog)
#2
10、__new__方法(参考3.2)和单例模式
单例模式:一种常用的软件设计模式
目的:确保某个类中只有一个实例存在
实现步骤:利用类属性保存初次创建的实例对象,第二次实例化对象的时候判断类属性是否保存实例对象,如果有就返回类属性保存的,如果没有就调用父类__new__方法创建新的实例对象
class Student(object):
__instance=None
def __init__(self,name,age):
self.name=name
self.age=age
pass
def __new__(cls, *args, **kwargs):
if not cls.__instance:
cls.__instance=super(Student,cls).__new__(cls)#没有保存实例,就保存
return cls.__instance
else:
return cls.__instance
pass
pass
xm=Student('LH',19)
print(id(xm))
xh=Student('XH',20)
print(id(xh))
# 2390515351312
# 2390515351312
#id相同,说明实例化两次对象,都是同一对象
11、异常处理
语法格式:
try:
可能出现错误的代码块
except:
出错后执行的代码块
else:
没有错误执行的代码块
finally:
不管有没有出错都要执行的代码块
try:
x=x/2
print(x)
except NameError as msg:
print(msg)
#name 'x' is not defined
Python内置异常类型:
Exception:万能类型、可以捕获所有异常
自定义异常
- 要直接或间接继承Error或Exception
- 用raise关键字抛出异常
class Toolong(Exception):
def __init__(self,leng):
self.leng=leng
pass
def __str__(self):
return '你的长度为%s,过长了。。'%self.leng
pass
def name_test():
name=input('请输入你的名字:')
try:
if len(name)>4:
raise Toolong(len(name))
else:
print(name)
pass
pass
except Toolong as msg:
print(msg)
finally:
print('执行完毕')
name_test()
# 请输入你的名字:chenq
# 你的长度为5,过长了。。
# 执行完毕
12、动态添加属性和方法
动态语言能在运行时改变其结构;Python、php、JavaScript是动态语言,c、c#、Java是静态语言
所有,Python在程序运行过程中添加属性和方法
12.1 动态添加属性
#动态添加属性
class Animal:
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s 年龄:%s'%(self.name,self.age))
pass
cat=Animal('小猫',2)
cat.say()
cat.color='花色'#给对象动态添加属性
print(cat.color)
Animal.color='白色'#给类动态添加属性
print(Animal.color)
#小猫 年龄:2
#花色
#白色
12.2动态添加方法(需要用到types模块)
语法:
import types#引入代码块
def 方法名(self):
xxx
pass
class 类名:
xxx
pass
实例名=类名()
实例名.引入方法的新名字=types.MethodType(方法名,实例名)#动态添加方法
实例名.引入方法的新名字()#调用新方法
import types#引入types模块
def run(self):
print("%s 岁的%s在跑。。"%(self.age,self.name))
class Animal:
def __init__(self,name,age):
self.name=name
self.age=age
pass
cat=Animal('小猫',2)
cat.run=types.MethodType(run,cat)#动态添加方法
cat.run()#调用方法
#2 岁的小猫在跑。。
12.3 动态绑定类方法和静态方法
语法:类名.新名字=类方法名
@classmethod
def classTest(cls):
print('这是个类方法')
pass
@staticmethod
def staticTest():
print("这是个静态方法")
pass
class Animal():
pass
#动态给类添加类方法、静态方法
Animal.classnewname=classTest
Animal.staticnewname=staticTest
#调用类方法,静态方法
Animal.classnewname()
Animal.staticnewname()
13、__slots__属性
作用:限制可以添加的实例属性
class Animal(object):
__slots__ = ('name','age','weight')#限制添加的属性
def __str__(self):
return '%s 。。。%s'%(self.name,self.age)
pass
xh=Animal()
xh.name='小花'
xh.age=20
xh.weight=45
# xh.height 被限制,运行会报错
print(xh)#小花 。。。20
__dict__:所有可用的属性都存储在这,占用内存高;使用__slots__后,实例中不再有__dict__
class Animal(object):
# __slots__ = ('name','age','weight')#限制添加的属性
def __str__(self):
return '%s 。。。%s'%(self.name,self.age)
pass
xh=Animal()
xh.name='小花'
xh.age=20
xh.weight=45
# xh.height 被限制,运行会报错
print(xh)#小花 。。。20
print(xh.__dict__)#没有设置__slots__时,所有可用的属性存储在这
# 小花 。。。20
# {'name': '小花', 'age': 20, 'weight': 45}
slots的作用:限制实例的属性;节约内存空间
子类继承父类时,若不声明slots,不继承父类的slots;声明slots后,子类限制属性=父类+自身
class Animal(object):
__slots__ = ('name','age','weight')#限制添加的属性
def __str__(self):
return '%s 。。。%s'%(self.name,self.age)
pass
class Dog(Animal):
__slots__ = ('color')#未声明时,不继承父类__slots__,声明后,限制属性=父类+自身
pass
dog=Dog()
dog.color='白色'
print(dog.color)
# 白色