Python进阶语法
1.函数式编程
函数也是一种数据,数据类型是function,函数式编程是基于函数进行的一系列操作。
1.1函数作为参数
即回调函数(callback),在调用函数时使用函数作为形参和实参
-
“工具函数”:只是找到对应函数的工具,不负责内部处理,而是由业务函数来处理
-
“业务函数”:也是传入的函数,即回调函数,用于对数据做处理,作为参数被别的函数调用
可以类比前面学过的map(),filter(),reduce()等函数,它们的第一个参数都是函数,负责处理的是匿名函数(也可以单独调用函数来使用),第二个参数是传入的待处理的数据
#工具函数
def find_odd(num,tool):
for el in num:
if el % 2 == 1:
# print("找到其中一个奇数:",el)
tool(el) #调用函数,在这里是一个形式函数(形参)
l1=[1,2,3,4,5,6,7,8,9,10]
#业务函数1
def print_odd(el):
print("找到其中一个奇数:",el)
#业务函数2
def print_odd2(el):
print("处理后的奇数:",el**2)
find_odd(l1,print_odd)
find_odd(l1,print_odd2)
#自己定义的map(),fliter(),reduce()函数:
def fun_filter(tool,l):
l2=[]
for el in l:
if tool(el):
l2.append(el)
return l2
def fun_map(tool,l):
l3=[]
for el in l:
re=tool(el)
l3.append(re)
return l3
def fun_reduce(tool,l,first_num):
if len(l)==0:
first_num=l[0]
start=1
else:
start=0
for el in l[start:]:
first_num=tool(first_num,el)
return first_num
##处理map的业务函数,x是传入的值
def fun1(x):
return x
#处理filter的业务函数,x是传入的值
def fun2(x):
return x%2==0
#处理reduce的业务函数,y是传入的值,x是初始的值,也是下一次递归调用时的初始值
def fun3(x,y):
return x+y
l1=[1,2,3,4,5]
print(fun_map(fun1,l1))
print(fun_filter(fun2,l1))
print(fun_reduce(fun3,l1,10))
#自己的示例,写一个程序,一个平台是查询钱包余额,另一个平台得到钱包金额的数据后自动抢对应价位的live票
#通过钱包余额记录票的种类,属于工具函数
def ticket_buy(fun_c,d):
cash=fun_c(d)
if cash>=888:
return "ss特等席"
elif cash>=588 and cash<888:
return "s特等席"
elif cash>=288 and cash<588:
return "A特等席"
else:
return "无席位"
#记录金额总数,是回调函数
def wallet_check(d):
return d['百元']*100+d['五十元']*50+d['十元']*10+d['一元']*1
cash_dict_str=(input("请输入钱包对应的金额和数量('百元','五十元','十元','一元'),以空格隔开:"))
cash_dict_list=cash_dict_str.split()
cash_dict={cash_dict_list[i]:int(cash_dict_list[i+1]) for i in range(0,len(cash_dict_list),2)}
print(ticket_buy(wallet_check,cash_dict))
1.2函数作为返回值
即函数的闭包(block),初步理解,函数的内部还有函数,内部的那个函数就是闭包
闭包必须满足以下三个条件:
- 必须有一个内嵌函数
- 内嵌函数必须引用外层函数中变量
- 外部函数返回值必须是内嵌函数
下面通过几个方面示例:
- 由于函数的功能不同,所以函数之间的变量互不干扰,需要分开作用域
def 所有函数():
def fun1():
内容
def fun2():
内容
- 函数内部的变量,在函数外部不能被直接访问,但是可以间接访问
- 由于函数的定义和调用的作用域不同,并且函数的调用是在函数被定义的作用域执行的
- 通过返回这个闭包函数,就可以实现在外部访问函数内部的变量
#用一个app之间的授权交流的例子来理解
def sakanaction_app():
point_all=3000
def point_refresh(type):
#访问外部变量并修改
nonlocal point_all
if type=='video':
point_all+=100
elif type=='text':
point_all+=50
elif type=='message':
point_all+=20
else:
point_all+=1
print(point_all)
return point_refresh
#用于获取用户行为的app,user_type是在tixplus_app内用于调用函数而定义的
def tixplus_app():
user_type=sakanaction_app()
user_type('video')
user_type('text')
user_type('message')
user_type('none')
tixplus_app()
#购物的例子
def del_goods(money):
#闭包的函数
def buy_goods(goods,price):
nonlocal money
if money>=price:
money-=price
print(f"购买了{goods},剩余余额为{money}")
else:
return f'余额不足,无法购买{goods}'
#返回闭包函数
return buy_goods
#用一个变量接受函数的返回值,其类型也是函数
good=del_goods(2000)
good('毛巾',1800)
good('勋章',1500)
good('立牌',1000)
- 闭包不一定是单独定义的函数,只要有单独的作用域就行(如后面学的装饰器,类,对象,数据容器(内部有函数)就是闭包)
–重要:一个例子理解回调和闭包–
def fun(num=None,n=None):
print(n)
return {"fun": lambda x:fun(x,num)}
#情形1
type_a=fun(0)
type_a["fun"](1)
type_a["fun"](2)
type_a["fun"](3)
#情形2
type_b=fun(0)["fun"](1)["fun"](2)["fun"](3)
#情形3
type_c=fun(0)["fun"](1)
type_c["fun"](2)
type_c["fun"](3)
#流程分析伪代码,使用{}来代表一个作用域
'''
全局作用域global{
#情形1
语句type_a=fun(0)调用fun()函数,传入参数0
【标识符fun】
A1>>>fun(0)->{
标识符num=0
标识符n=None
print(n)
这里num=0,故传入
return {"fun": lambda x:fun(x,0)}
A1>>>type_a["fun"](1)这里是在对函数a的返回的字典取键值为"fun"调用fun()函数,传入参数1,所以调用时还是在A1作用域内
{
标识符num=1
在本作用域未找到n的值,在外层作用域用寻找
return {"fun": lambda x:fun(1,0)}传入数据num的值为1
}
A1>>>type_a["fun"](2)这里是在对函数a的返回的字典取键值为"fun"调用fun()函数,传入参数2,所以调用时还是在A1作用域内
{
标识符num=2
在本作用域未找到n的值,在外层作用域用寻找
return {"fun": lambda x:fun(2,0)}传入数据num的值为2
}
A1>>>type_a["fun"](3)这里是在对函数a的返回的字典取键值为"fun"调用fun()函数,传入参数3,所以调用时还是在A1作用域内
{
标识符num=3
在本作用域未找到n的值,在外层作用域用寻找
return {"fun": lambda x:fun(3,0)}传入数据num的值为3,所以返回fun(3,0)
}
}
A2>>>type_a["fun"](1)这里是在返回的字典中调用fun()函数,所以调用时要新生成一个作用域A2
{
标识符num=1
标识符n=0
print(n)输出0
return {"fun": lambda x:fun(x,num)}
}
A2>>>type_a["fun"](2)同理
{
标识符num=1
标识符n=0
print(n)输出0
return {"fun": lambda x:fun(x,num)}
}
A2>>>type_a["fun"](3)同理
{
标识符num=3
标识符n=0
print(n)输出0
return {"fun": lambda x:fun(x,num)}
}
#情形2
type_b=fun(0)["fun"](1)["fun"](2)["fun"](3)首先会执行第一层type_b=fun(0),传入num=0
B1>>>type_b=fun(0)
{
标识符num=0
标识符n=None
print(n)输出None
return {"fun": lambda x:fun(x,num)}
B1>>>type_fun(0)["fun"](1)传入参数为1,此时是在该返回的字典中调用fun()函数,所以执行该段代码仍是在B1作用域内
{
标识符num=1
在本作用域未找到n的值,在外层作用域用寻找n=0,并作为参数传给fun()函数
return {"fun": lambda x:fun(1,0)}
}
}
B2>>>type_fun(0)["fun"](1)这里对返回的字典调用fun()函数,所以要在新生成一个作用域B2
{
标识符num=1
标识符n=0
print(n)输出0
return {"fun": lambda x:fun(x,num)}
B2>>>type_fun(0)["fun"](1)["fun"](2)
{
标识符num=2
在本作用域未找到n的值,在外层作用域用寻找n=1,并作为参数传给fun()函数
return {"fun": lambda x:fun(2,1)}
}
}
B3>>>type_fun(0)["fun"](1)["fun"](2)
{
标识符num=2
标识符n=1
print(n)输出1
return {"fun": lambda x:fun(x,num)}
B2>>>type_fun(0)["fun"](1)["fun"](2)["fun"](3)
{
标识符num=2
在本作用域未找到n的值,在外层作用域用寻找n=1,并作为参数传给fun()函数
return {"fun": lambda x:fun(,)}
}
}
B4>>>type_fun(0)["fun"](1)["fun"](2)["fun"](3)
{
标识符num=3
标识符n=2
print(n)输出2
return {"fun": lambda x:fun(3,2)}
}
#情形3
type_c=fun(0)["fun"](1)
type_c["fun"](2)
type_c["fun"](3)
C1>>>type_c=fun(0)
{
标识符num=0
标识符n=None
print(n)输出None
return {"fun": lambda x:fun(x,num)}
C1>>>type_c(0)["fun"](1)
{
标识符num=1
未找到n的值,在外层作用域用寻找n=0,并作为参数传给fun()函数
return {"fun": lambda x:fun(1,0)}
}
}
C2>>>type_c(0)["fun"](1)
{
标识符num=1
标识符n=0
print(n)输出0
return {"fun": lambda x:fun(x,num)}
C2>>>type_c["fun"](2)
{
标识符num=2
未找到n的值,在外层作用域用寻找n=1,并作为参数传给fun()函数
return {"fun": lambda x:fun(2,1)}
}
C2>>>type_c["fun"](3)
{
标识符num=3
未找到n的值,在外层作用域用寻找n=1,并作为参数传给fun()函数
return {"fun": lambda x:fun(3,1)}
}
}
C3>>>type_c)["fun"](2)
{
标识符num=2
标识符n=1
print(n)输出1
return {"fun": lambda x:fun(x,num)}
}
C4>>>type_c["fun"](3)
{
标识符num=3
标识符n=1
print(n)输出1
return {"fun": lambda x:fun(x,num)}
}
1.3装饰器
- 本质是回调和闭包的结合,其本身也是一种函数
- 在装饰器下的函数,在调用时要在装饰器内部执行,不单单执行函数本身
- 使用的格式:@装饰器自定义命名,将下面的函数作为参数放入装饰器中执行
def decorator(fun):
def block():
语句1
fun()
语句2
return block
@decorator
def test():
语句1
#被装饰的函数
test()#这里看起来是在调用test,实际是在调用装饰器里的闭包
1.3.1基本装饰器
自身不带参数
def decorator(fun):
def block():
print("装饰器开始使用")
fun()
print(f"装饰器结束使用")
return block
@decorator
def test():
print("test函数被调用")
test()
#输出 装饰器开始使用 test函数被调用 装饰器结束使用
1.3.2带参装饰器
带有参数的装饰器,带了两层闭包,是装饰器的“套娃”
- 带参装饰器示例
def decorator(fun):
def block(num):
print("装饰器开始使用")
fun()
print(f"装饰器结束使用",num)
return block
@decorator1()
def test():
print("test函数被调用")
- 被调用的函数调用时传参:
def decorator1(num):
def decorator2(fun):
def block():
print("装饰器开始使用",num)
fun()
print("装饰器结束使用")
return block
return decorator2
@decorator1(3)
def test():
print("test函数被调用")
test()
#输出
装饰器开始使用 3
test函数被调用
装饰器结束使用
1.3.3链装饰器
用装饰器来装饰装饰器装饰过的函数,和上面类似,但形式不同
def decorator1(num):
def block(fun):
def block1():
print("装饰器开始使用",num)
fun()
print("装饰器结束使用",num)
return block1
return block
def decorator2(fun):
def block():
print("装饰器开始使用2")
fun()
print("装饰器结束使用2")
return block
@decorator1(3)
@decorator2
def test():
print("test函数被调用")
#输出
装饰器开始使用 3
装饰器开始使用2
test函数被调用
装饰器结束使用2
装饰器结束使用 3
2.面向对象编程
是一种通过组织对象来设计程序的编程方法
类>>>对象
2.1类与对象
2.1.1类Class
类是对一类对象的抽象,定义了对象的属性(变量)和方法(函数),如之间学过的list,tuple等都是官方的类
- 数据成员:表明对象的特征。 相当于变量
- 方法成员:表明对象的功能。 相当于函数
- 通过
class
关键字定义类。 - 类的创建语句语法:
class 类名 (继承列表):
实例属性(类内的变量) 定义
实例方法(类内的函数method) 定义
类变量(class variable) 定义
类方法(@classmethod) 定义
静态方法(@staticmethod) 定义
#定义示例
class animal:
name='sakana'
year=10
def fun(arg):
print("这是一个类方法(函数)",arg)
sakana=animal()
2.1.2对象
对象是类的实例化,是类的实际数据存储,具有类所定义的属性和方法。
-
变量存储的是实例化后的对象地址
-
类参数按照初始化方法的形参传递
- 对象是类的实例,具有类定义的属性和方法。
- 通过调用类的构造函数来创建新对象。
- 每个对象有自己的状态,地址不同,但共享方法(类的内部)。
class animal:
name='sakana'
year=10
sakana1=animal()
#调用对象的属性/方法
print(sakana1.name)
使用类创建对象时,可以使用官方方法init初始化成员的值:
#_init_这个函数是一个魔术方法
class Animal:
#创建对象时调用:类名(参数)会传给函数_init_
def _init_(arg,name,year):
print(arg,name,year) #<__main__.Animal object at 0x000002A2893356A0> 空棘鱼 834
#给对象s1添加新新属性,属性名是age
arg.age=year-5
#将"空棘鱼",834传入类的_init_函数
s1=Animal("空棘鱼",834)#这里有一个隐式操作,_init_(s1,"空棘鱼",834),和上面调用其他方法相同,对象会作为参数传入
print(s1.name,s1.year,s1.age) #空棘鱼 834 829
2.2.属性和方法
2.2.1实例属性
实例.属性
class Animal:
type='fish'
def __init__(self,name,year):
print(self,name,year)
#给对象s1添加新新属性,属性名是age
self.name=name
self.year=year
self.age=year-5
#将"空棘鱼",834传入类的_init_函数
s1=Animal("空棘鱼",834)
s2=Animal("阿姆斯鱼",194)
print(s1.type,s1.name,s1.year,s1.age) #fish 空棘鱼 834 829
print(s2.type,s2.name,s2.year,s2.age) #fish 阿姆斯鱼 194 189
2.2.2实例方法
class 类名(继承列表):
def 实例方法名(self, 参数1, 参数2, ...):
语句块
- 实例方法就是函数,至少有一个指向实例对象的形参self
class Num:
num=0
def __init__(self, num):
print(num) #输出传入的num的值10 20
def change_num(self, num):
self.num=num
print(num)
s1=Num(10)
s2=Num(20)
print(s1.num)
print(s2.num)
s1.num=30
print(s1.num) #直接修改Num属性num的值 30
s1.change_num(40) #调用Num的change_num方法修改num的值
print(s1.num) #40
print(s2.num) #s2的num值不变
实例方法内还可以调用其他方法(包括自己,此时就是递归)
class Num:
num=20
def __init__(self, num):
print(num) #输出传入的num的值10
def fun1(self):
#这里看似是在fun2定义之前调用了fun2方法,但是实际上在实例s1创建的时候就已经开辟了这个方法的空间
self.fun2()
def fun2(self):
print(self.num)
s1=Num(10)
s1.fun1()
- 自己写的一个综合示例
class Band:
name="unkonwn"
country = "unkonwn"
year=0
def __init__(self,name,country,year):
self.name=name
self.country=country
self.year=year
def is_new(self):
if self.year>2010:
return self.print()
else:
return 0
def print(self):
print(f"乐队{self.name}来自{self.country}成立于{self.year}年")
band1=Band("sakanaction","JAPAN",2007)
band2=Band("Chinesefootball","CHINA",2015)
band1.is_new()
band2.is_new()
2.2.3类属性
类属性是属于类的属性,不属于类的实例
- 一般是对象公用的属性
- 类可以使用,对象也可以使用
class Animal:
type='fish'
def __init__(self,name,year):
print(self,name,year)
#给对象s1添加新新属性,属性名是age
self.name=name
self.year=year
self.age=year-5
#将"空棘鱼",834传入类的_init_函数
s1=Animal("空棘鱼",834)
#类来访问属性
print(Animal.type)
2.2.4类方法
类属性是属于类的方法,不属于类的实例
- 类方法需要使用**@classmethod装饰器**定义
- 类方法至少有一个形参用于绑定类,约定为 cls
- 类和该类的实例都可以调用类方法
- 类方法不能访问此类创建的对象的实例属性
class Animal:
type='fish'
def __init__(self,year):
print(self,year)
#给对象s1添加新新属性,属性名是age
self.age=year-5
#一般是类使用的方法,是一种隐式操作
def get_type1(self):
print(self.type)
@classmethod
def get_type2(cls):
print(cls.type)
#实例对象可以访问的属性和方法:type属性,age属性,get_type1()方法,get_type2()方法(需要传参)
s1=Animal(834)
print(s1.age)
#类可以访问的属性和方法:
#print(Animal.age),报错,因为类没有age属性,age属性是实例对象s1的属性
print(Animal.type)
Animal.age=5
Animal.get_type1(Animal) #类可以调用普通方法,但是需要传入该类作为参数
#类使用类方法不需要传本身
Animal.get_type2()
一个使用类方法和普通方法创建单例的实例
2.2.5静态方法
定义在类的内部,作用域是类内部
- 使用@staticmethod装饰器定义
- 不需要self和cls参数
- 可以通过类或类实例调用
- 可以访问类属性,但不能访问实例属性
class Num:
def fun1 (self):
print("实例方法")
@classmethod
def fun2(cls):
print("类方法")
@staticmethod
def fun3():
print("静态方法")
@staticmethod #这个静态对象通常当作一种工具
def sigmond(x):
return 1/(1+pow(2.71828,-x))
#类
Num.fun1(Num)
Num.fun2()
Num.fun3()
#静态方法需要传入参数
print(Num.sigmond(10))
#实例对象
n1=Num()
n1.fun1()
n1.fun2()
n1.fun3()
print(n1.sigmond(10))
2.2.6构造方法
构造方法__new__()
:
- 负责对象的 创建 和内存分配的。
- 在对象实例化时被调用,负责返回一个新的对象实例。
- 通常不需要显式地定义
__new__()
方法,Python会调用基类 object 的__new__()
方法。
class MyClass:
def __new__(self,*args):
print("调用 __new__ 方法,创建对象")
return super().__new__(self) #super是一种继承
def __init__(self, value):
print("调用 __init__ 方法,初始化对象")
self.value = value
# 创建对象
obj1=MyClass(10)
print(obj1)
2.2.7初始化对象
对象被创建后,Python会调用 __init__()
来初始化对象的属性。(前面已经学习)
2.2.8魔术方法
魔术方法是一种特殊的方法,用双下划线包裹,例如__init__
,__str__
,__add__
等。这些方法允许用户自定义类的行为,以便与内置Python功能(如+运算符、迭代、字符串表示等)交互。
常见魔术方法
重要:
__init__(self, ...)
: 初始化对象,通常用于初始化对象的属性。__len__(self)
: 定义对象的长度,可通过len(object)
调用。通常在自定义容器类中使用。class Images: def __init__(self, path): self.path = path self.arr=['1.jpg','2.jpg'] def __len__(self): return len(self.arr) imgs=Images("./imgs") count=len(imgs) print(count)
__getitem__(self, key)
: 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key]
。class Images: def __init__(self, path): self.path = path self.arr=['1.jpg','2.jpg'] def __len__(self): return len(self.arr) def __getitem__(self, index): return self.arr[index] obj1=Images("./imgs") count=len(obj1) print(count) print(obj1[0])
__setitem__(self, key, value)
: 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如,object[key] = value
。class Images: def __init__(self, path): self.path = path self.arr=['1.jpg','2.jpg'] def __len__(self): return len(self.arr) def __getitem__(self, index): #取值 return self.arr[index] def __setitem__(self, index, value): #赋值 self.arr[index]=value print(index,value) obj1=Images("./imgs") count=len(obj1) print(count) print(obj1[0]) # obj1[10]="Hello" #__setitem__(obj,10,"Hello"),列表越界 obj1[1]=2 #__setitem__(obj,1,2)调用setitem修改下标1的值 print(obj1[1]) #2
__iter__(self)
: 定义迭代器,使对象可迭代,可用于for
循环。(后面单独学习)__next__(self)
: 定义迭代器的下一个元素,通常与__iter__
一起使用。(后面单独学习)__call__(self, other)
是一个特殊的方法(也称为“魔法方法”),它允许一个对象像函数一样被调用。(后面机器学习中的预测会学到)class Images: def __init__(self, path): self.path = path self.arr=['1.jpg','2.jpg'] def __call__(self,*args,**kwargs): print("调用__call__",args,kwargs) obj1=Images("./obj") print(callable(obj1)) #True,添加了call魔术方法后,就可以作为函数调用 obj1(10,20,30,a=100,b=200)
一般:
__str__(self)
: 定义对象的字符串表示形式,可通过str(object)
或print(object)
调用。如可以返回一个字符串,若要输出则会输出对应字符串而不是类型内存等。__repr__(self)
: 定义对象的“官方”字符串表示形式,通常用于调试。可通过repr(object)
调用。__delitem__(self, key)
: 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如,del object[key]
。__add__(self, other)
: 定义对象相加的行为,使对象可以使用+
运算符相加。例如,object1 + object2
。__sub__(self, other)
: 定义对象相减的行为,使对象可以使用-
运算符相减。__lt__(self, other)
: 定义对象小于其他对象的行为,使对象可以使用<
运算符比较。__gt__(self, other)
: 定义对象大于其他对象的行为,使对象可以使用>
运算符比较。__eq__(self, other)
: 定义对象相等性的行为,使对象可以使用==
运算符比较。
2.3 OOP基本特性
OOP的四大基本特性是封装、继承、多态和抽象。
2.3.1 封装
-
封装是指将对象的属性和方法包装在一起,对外隐藏实现细节,只提供必要的接口给外部访问。
-
通过
__init__
方法初始化属性,并通过方法来操作这些属性。 -
以
__
开头的属性或方法是私有的,在子类和类外部无法直接使用 -
可使用“私有”属性和“公有”方法控制外部访问。
class Images: def __init__(self, path): self.path = path self.arr=['1.jpg','2.jpg'] def __len__(self): return len(self.arr) def __getitem__(self, index): return self.arr[index] obj1=Images("./imgs") count=len(obj1) print(count) print(obj1[0])
2.3.2继承
两个或多个类之间的关系,父类的属性和方法可以由子类继承
2.3.2.1概念
-
继承/派生
- 继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
- 派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
-
继承/派生的作用
- 用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
- 在不改变基类的代码的基础上改变原有类的功能
-
继承/派生名词:
- 基类(base class)/超类(super class)/父类(father class)
- 派生类(derived class)/子类(child class)
2.3.2.2继承实现
继承语法:
class 类名(父类1,父类2…)
#这是一个单例继承,若父类有多个,则为多类继承
class Animal:
def type_num(self):
self.num=500
class Dog(Animal):
def type_num(self):
self.num=1000
class fish(Animal):
def type_num(self):
self.num=2000
2.3.2.3覆盖
也是重写,即子类中与父类相同的方法,但实现不同的功能
class Animal:
def __init__(self,name):
self.name=name
def type_num(self):
self.num=500
print(self.num)
def size(self):
self.size=20
print(self.size)
class Fish(Animal):
def __init__(self,name,age):
#调用父类的构造函数
super().__init__(name)
self.age=age
def type_num(self):
self.num=100
print(self.num)
fish1=Fish('sakana',5)
#调用的父类Animal的size方法
fish1.size() #20
#Fish类覆盖了父类的type_num方法
fish1.type_num() #100
2.3.3多态
是指同一个方法在不同对象上的不同功能体现,使用继承和重写来实现
class Band:
country="Japan"
def Name(self):
self.name="sakanaction"
print(self.name)
class Vocal(Band):
def Name(self):
self.name="山口一郎"
print(self.name)
class Guitar(Band):
def Name(self):
self.name="岩寺基晴"
print(self.name)
class Band_name(Band):
def Found_year(self):
self.year="2018"
print(self.year)
vocal=Vocal()
#重写了Name方法
vocal.Name() #山口一郎
guitar=Guitar()
#重写了Name方法
guitar.Name() #岩寺基晴
band=Band_name()
band.Name() #sakanaction
#调用了父类的属性
print(band.country) #Japan
2.3.4重载
也是重写,就是使用前面的魔术方法,来重载对象转字符串,内建函数,运算符
2.3.4.1 对象转字符串重写
-
对象转字符串函数重写方法
-
str() 函数的重载方法:
-
def __str__(self)
- 如果没有
__str__(self)
方法,则返回repr(obj)函数结果代替
- 如果没有
-
-
-
str/repr函数重写示例
class MyNumber: """此类用于定义一个自定义的类,用于演示str/repr函数重写""" def __init__(self, value): """构造函数,初始化MyNumber对象""" self.data = value def __str__(self): """转换为普通字符串""" return "%s" % self.data n1 = MyNumber("一只猫") n2 = MyNumber("一只狗") print("str(n2) ===>", str(n2))
2.3.4.2 内建函数重写
-
__abs__
abs(obj) 函数调用 -
__len__
len(obj) 函数调用 -
__reversed__
reversed(obj) 函数调用 -
__round__
round(obj) 函数调用 -
内建函数 重写示例
# file : len_overwrite.py class MyList: def __init__(self, iterable=()): self.data = [x for x in iterable] def __repr_(self): return "MyList(%s)" % self.data def __len__(self): print("__len__(self) 被调用!") return len(self.data) def __abs__(self): print("__len__(self) 被调用!") return MyList((abs(x) for x in self.data)) myl = MyList([1, -2, 3, -4]) print(myl.data) print(len(myl)) print(abs(myl).data)
2.3.4.2 运算符重载
-
运算符重载是指让自定义的类生成的对象(实例)能够使用运算符进行操作
-
运算符重载的作用
- 让自定义类的实例像内建对象一样进行运算符操作
- 让程序简洁易读
- 对自定义对象将运算符赋予新的运算规则
-
运算符重载说明:
- 运算符重载方法的参数已经有固定的含义,不建议改变原有的意义
方法名 | 运算符和表达式 | 说明 |
---|---|---|
__add__(self, rhs) | self + rhs | 加法 |
__sub__(self, rhs) | self - rhs | 减法 |
__mul__(self, rhs) | self * rhs | 乘法 |
__truediv__(self, rhs) | self / rhs | 除法 |
__floordiv__(self, rhs) | self // rhs | 地板除 |
__mod__(self, rhs) | self % rhs | 取模(求余) |
__pow__(self, rhs) | self ** rhs | 幂 |
2.3.5对象的成员操作
对象在对成员进行操作时,在取值和赋值的情况下的操作也不同
class Band:
country="Japan"
def Name(self):
self.name="sakanaction"
print(self.name)
class Vocal(Band):
def Name(self):
self.name="山口一郎"
print(self.name)
vocal=Vocal()
#重写了Name方法
vocal.Name() #山口一郎
#取值操作,本类里没有该成员,故去父类的成员里找
print(vocal.country) #Japan
#赋值操作,该类里没有该成员,则直创建并赋值
vocal.born_place='北海道'
print(vocal.born_place) #北海道
综合实例
class Band:
def __init__(self, name):
self.name = name
print(self.name)
def play_song(self, song):
print(f"{self.name} is playing '{song}'")
class Song(Band):
def __init__(self, name, members):
self.name = name
self.members = members
print(f"{self.name}由成员{self.members}组成")
def play_song(self,song,num):
print(f"{self.members[num]} is playing '{song}'")
def album(self,name):
self.al_name=name
print(f"{self.mu_name} 来自专辑'{self.al_name}'")
band=Song("sakanaction",["山口一郎","岩寺基晴","冈崎英美","江岛启一","草刈爱美"])
band.play_song("Eureka",1)
2.3.6私有属性
有一部分属性只能在类的内部使用,称为私有属性,__x来定义私有属性
#__属性名
class Animal:
__x=100
def fun(self):
print(self.__x)
s=Animal()
print(s.__x) #报错,'Animal' object has no attribute '__x'
s.fun() #100,可以通过函数来调用私有属性,有点类似于闭包的思想
2.4.super函数
用于调用父类的一种方法
2.4.1实现实例
在类的内部调用父类方法:
class Band:
country="Japan"
def Name(self):
self.name="sakanaction"
print(self.name)
def Country(self):
print(self.country)
class Vocal(Band):
#重写了父类的方法,但是想在父类已有的代码的基础上再添加一些代码
def Name(self):
super().Name() #super()表示调用父类,类似Band.Name(),依然是self在调用Name(),这种使得父类的Name方法和自己类的Name方法可以一起调用
self.name="山口一郎"
print(self.name)
vocal=Vocal()
vocal.Name() #只调用了重写的方法 输出:山口一郎 使用super()后输出:sakanaction 山口一郎
vocal.Country() #只调用了父类的方法
对象在类的外部调用父类方法:
class Band:
def Name(self):
self.name="sakanaction"
print(self.name)
class Vocal(Band):
def Name(self):
self.name="山口一郎"
print(self.name)
vocal=Vocal()
super(Vocal,vocal).Name() #sakanaction
#总结:
1.把对象vocal作为第一个参数传入self,执行子类Vocal的父类Band的Name函数
2.super(当前类,self)是标准写法,super()是自动推论过当前类和self对象后的简化
2.4.2 super().__init__()
通过 super(). __init__() 调用父类构造函数,以确保父类的构造函数被正确调用和初始化。
class Band:
def __init__(self,name,year):
self.name=name
self.year=year
print(name,year)
class Vocal(Band):
def __init__(self,name,year,age):
#在子类中调用父类的初始化对象的方法
super().__init__(name,year)
self.age=age
band1=Vocal('sakanaction',10,44)
使用**super()**的理由:
- **代码重复:**直接调用父类的方法,避免代码的重复性
- **正确初始化:**确保父类的初始化逻辑(如设置属性、分配资源等)被执行。
3.迭代器和生成器
3.1迭代器
**迭代器(Iterator)**是一种对象,实现了Python的迭代协议(__iter__()
和 __next__()
方法)。通过迭代器,可以逐个访问容器中的元素。
3.1.1特性
-
按需生成数据,节省内存。
-
实现协议:
__iter__()
:返回自身。__next__()
:返回下一个元素;如果没有更多元素,则抛出StopIteration
异常。
-
可迭代对象与迭代器不同:
-
可迭代对象实现
__iter__()
方法,返回一个迭代器。 -
迭代器既实现
__iter__()
又实现__next__()
,而迭代器不能使用next方法(自定义除外)。 -
迭代器只能迭代一次,而可迭代对象可以多次迭代
-
-
iter函数会返回一个可迭代对象的迭代器
list1=[10,20,30]
iiterator1=iter(list1)
#使用next方法迭代下一个数据
print(next(iiterator1)) #10
#迭代器只会往前取,不会后退
for i in range(2):
print(next(iiterator1),"迭代了") #20 迭代了 30 迭代了
#没有结果,只能迭代一次
for i in range(2):
print(next(iiterator1),"迭代了")
for i in list1:
print(i)
#列表本身也是个迭代器,但可以多次迭代
for i in list1:
print(i)
3.1.2自定义迭代器
#类中实现了__iter__()和__next__()方法,用这个类创建的对象就是一个可迭代对象
#__iter__():1.要求返回一个迭代器对象2.iter函数调用传入用这个类创建的对象,就会调用__iter__()方法,来返回一个迭代器对象
#__next__():函数要求返回数据,使用next函数调用传入一个迭代器对象,就会调用__next__()方法,来返回数据
class Boom:
def __init__(self, x):
self.x = x
def __iter__(self):
return self
def __next__(self):
if self.x > 0:
self.x -= 1
return self.x
else:
#抛出异常
raise StopIteration
b1= Boom(5)
iterator1 = iter(b1)
for i in iterator1:
print(i)
3.2生成器
生成器(Generator)是一种特殊的迭代器,通过函数定义,用 yield 语句生成值。生成器可以自动实现迭代协议,无需手动实现 __iter__()
和 __next__()
。
3.2.1 特性
- 简洁:比手动实现迭代器更易写。
- 惰性计算:生成器在每次调用 next() 时生成一个值,而不是一次性生成所有值。
- yield:暂停函数执行并返回值,保留函数的状态,以便下一次继续执行。
def fun():
print("函数运行1")
#暂停了程序,yield将函数的语句块的结果放入一个迭代器中
yield "块1"
print("函数运行2")
yield "块2"
print("函数运行3")
print("函数运行3")
yield "块3"
print("函数结束运行")
re=fun()
print("函数外部")
print(next(re))
print(next(re))
print(next(re))
print(next(re))
#输出
函数外部
函数运行1
块1
函数运行2
块2
函数运行3
函数运行3
块3
函数结束运行
#使用for循环简化代码
def fun():
data=['a','b','c','d']
for i in data:
yield i
re=fun()
print("函数外部1")
for i in re:
print(i)
一个例子来理解range方法的实现
def Myrange(start,end,step):
i=start-step
while True:
i+=step
if i>=end:
raise StopIteration
yield i
re=Myrange(2,10,2)
for i in re:
print(i) #raise StopIteration
3.2.2生成器表达式
基本语法:
(表达式 for 变量 in 可迭代对象 [if 真值表达式])#[]的内容可省略
示例:
gen_expr = (x**2 for x in range(5))
print(next(gen_expr)) # 0
print(next(gen_expr)) # 1
3.2.3课堂练习
#用生成器实现斐波那契数列
def fibonacci(n):
a,b=0,1
for _ in range(n):
yield b
a,b=b,a+b
fib_list=fibonacci(10)
for i in fib_list:
print(i,end=" ") #1 1 2 3 5 8 13 21 34 55
3.2.4应用场景
- 数据流处理:处理大文件或流式数据,避免内存耗尽。
- 无限序列生成:如斐波那契数列、素数序列。
- 管道化数据处理:与
itertools
模块结合使用。
3.3区别对比
特性 | 迭代器 | 生成器 |
---|---|---|
实现 | 通过类实现,手动定义方法 | 使用函数和 yield 定义,自动实现迭代协议 |
代码简洁性 | 代码较复杂 | 代码简单 |
状态管理 | 手动管理状态 | 自动保存函数的运行状态 |
惰性计算 | 支持 | 支持 |
示例应用 | 自定义复杂的迭代逻辑 | 简单的数据流生成 |
4.异常处理
-
用于提示上层语句有错误,可能会导致程序无法继续执行
-
使用异常处理的语法来继续执行后面无错的代码,来增强程序的鲁棒性(健壮性/robustness)
4.1 try语句
-
语法:
try: 可能出错的语句 except 错误类型 : 处理错误 except 错误类型 : 处理错误 except 错误类型 : 处理错误 ...... else: 没有错误时执行,处于异常时不执行,可不写 finally: 最终的处理语句,无论如何都会执行,可不写
-
示例
def fun1(num): print(int(num)) def fun2(num): print(num) try: fun1("sakana") except ValueError: print("该值不能转换为整数") fun2(50) #出错了,但是后续代码中的fun2函数仍然执行 #输出 该值不能转换为整数 50
4.2 raise语句
-
语法:
raise 异常类型: / raise 异常对象:
-
示例:
def get_band(): x = int(input('请输入乐队成员数量:')) if 3 <= x <= 6: return x # raise ValueError raise ValueError('乐队数量不在3-6之间') try: band = get_band() print(band) except ValueError as err: print("成绩输入有误 err=", err)
4.3错误类型
错误类型 | 说明 |
---|---|
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
ValueError | 传入无效的参数 |
AssertionError | 断言语句失败 |
StopIteration | 迭代器没有更多的值 |
IndexError | 序列中没有此索引(index) |
IndentationError | 缩进错误 |
OSError | 输入/输出操作失败 |
ImportError | 导入模块/对象失败 |
NameError | 未声明/初始化对象 (没有属性) |
AttributeError | 对象没有这个属性 |
<!-- 以下不常用 --> | |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
TypeError | 对类型无效的操作 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
OverflowError | 数值运算超出最大限制 |
FloatingPointError | 浮点计算错误 |
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
Exception | 常规错误的基类 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
WindowsError | 系统调用失败 |
LookupError | 无效数据查询的基类 |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError Python | 语法错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
以下为警告类型 | |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
关于特性将会被废弃的警告 | |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |