3.7 魔术方法
Python中的魔术方法(Magic Methods)是一种特殊的方法,它们以双下划线开头和结尾,例如__init__,__str__,__add__等。这些方法可以自定义类的行为,以便与内置Python功能(如+运算符、迭代、字符串表示等)交互。
以下是一些常用的Python魔术方法:
-
__init__(self, ...): 初始化对象,通常用于设置对象的属性。 -
__str__(self): 定义对象的字符串表示形式,可通过str(object)或print(object)调用。例如,您可以返回一个字符串,描述对象的属性。 -
__repr__(self): 定义对象的“官方”字符串表示形式,通常用于调试。可通过repr(object)调用。 -
__len__(self): 定义对象的长度,可通过len(object)调用。通常在自定义容器类中使用。 -
__getitem__(self, key): 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key]。 -
__setitem__(self, key, value): 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如,object[key] = value。 -
__delitem__(self, key): 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如,del object[key]。 -
__iter__(self): 定义迭代器,使对象可迭代,可用于for循环。 -
__next__(self): 定义迭代器的下一个元素,通常与__iter__一起使用。 -
__add__(self, other): 定义对象相加的行为,使对象可以使用+运算符相加。例如,object1 + object2。 -
__sub__(self, other): 定义对象相减的行为,使对象可以使用-运算符相减。 -
__eq__(self, other): 定义对象相等性的行为,使对象可以使用==运算符比较。 -
__lt__(self, other): 定义对象小于其他对象的行为,使对象可以使用<运算符比较。 -
__gt__(self, other): 定义对象大于其他对象的行为,使对象可以使用>运算符比较。 -
__call__(self, other)是一个特殊的方法(也称为“魔法方法”),它允许一个对象像函数一样被调用。
一些参考代码
1.__init__(self, ...): 初始化对象
class MyClass: def __init__(self, value): self.value = value obj = MyClass(42)
2.__str__(self): 字符串表示形式
class MyClass:
def __init__(self, value):
self.value = value
def __str__(self):
return f"MyClass instance with value: {self.value}"
obj = MyClass(42)
print(obj) # 输出:MyClass instance with value: 42
3.__repr__(self): 官方字符串表示形式
class MyClass:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"MyClass({self.value})"
#def __repr__(self):
#return f"Person(name={self.name}, age={self.age})"
obj = MyClass(42)
print(obj) # 输出:MyClass(42)
4.__len__(self): 定义对象的长度
class MyList: def __init__(self, items): self.items = items def __len__(self): return len(self.items) my_list = MyList([1, 2, 3, 4]) print(len(my_list)) # 输出:4
5.__getitem__(self, key): 索引操作
class MyDict:
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data.get(key)
my_dict = MyDict()
my_dict.data = {'key1': 'value1', 'key2': 'value2'}
print(my_dict['key1']) # 输出:value1
6.__setitem__(self, key, value): 赋值操作
class MyDict:
def __init__(self):
self.data = {}
def __setitem__(self, key, value):
self.data[key] = value
my_dict = MyDict()
my_dict['key1'] = 'value1'
print(my_dict.data) # 输出:{'key1': 'value1'}
7.__delitem__(self, key): 删除操作
class MyDict:
def __init__(self):
self.data = {}
def __delitem__(self, key):
del self.data[key]
my_dict = MyDict()
my_dict.data = {'key1': 'value1', 'key2': 'value2'}
del my_dict['key2']
print(my_dict.data) # 输出:{'key1': 'value1'}
8.__iter__(self): 迭代器
class MyIterable:
def __init__(self):
self.data = [1, 2, 3, 4]
def __iter__(self):
self.index = 0
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
my_iterable = MyIterable()
for item in my_iterable:
print(item)
# 输出:1, 2, 3, 4
9.__call__(self, *args, **kwargs)
class MyCallable:
def __call__(self, *args, **kwargs):
print("Called with arguments:", args, kwargs)
# 创建类的实例
my_callable = MyCallable()
# 像调用函数一样调用实例
my_callable(1, 2, 3, key="value")
#魔法方法
#修改魔法方法的实例
class Myclass:
def __init__(self,x,y,item):
self.x=x
self.y=y
self.data=data
#修改+运算符
def __add__(self,other):
return self.x+other.x,self.y+other.y
#修改实例对象的输出形式
def __str__(self):
return "x=%d,y=%d" % (self.x,self.y)
#return "Myclass"
def __len__(self):
return len(self.item)
data=[1,2,3]
d01=Myclass(10,20,data)
d02=Myclass(30,40)
#print(d01+d02)#报错 要使用__add__
print(d01)#打印的是存放地址 使用__str__返回的是想要的字符串 原理是覆盖
print(d01+d02)
prinr(len(d01))#报错 要使用__len__配套使用
4. 继承/派生
-
什么是继承/派生
-
继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
-
派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
-
-
为什么继承/派生
-
继承的目的是延续旧的类的功能
-
派生的目地是在旧类的基础上添加新的功能
-
-
继承/派生的作用
-
用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
-
在不改变基类的代码的基础上改变原有类的功能
-
-
继承/派生名词:
-
基类(base class)/超类(super class)/父类(father class)
-
派生类(derived class)/子类(child class)
-
4.1 单继承
-
单继承的语法:
class 类名(基类名): 语句块单继承说明:
-
单继承是指派生类由一个基类衍生出来的
-
单继承的示例1:
class Human: # 人类的共性 def say(self, what): # 说话 print("说:", what) def walk(self, distance): # 走路 print("走了", distance, "公里") class Student(Human): def study(self, subject): # 学习 print("学习:", subject) class Teacher(Human): def teach(self, language): print("教:", language) h1 = Human() h1.say("天气真好!") h1.walk(5) s1 = Student() s1.walk(4) s1.say("感觉有点累") s1.study("python") t1 = Teacher() t1.teach("面向对象") t1.walk(6) t1.say("一会吃点什么好呢")
4.2 多继承
Python支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
#另一个类,多继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
#多继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法
4.3 覆盖 override
-
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖
-
作用:
-
实现和父类同名,但功能不同的方法
-
-
覆盖示例
class A: def work(self): print("A.work 被调用!") class B(A): '''B类继承自A类''' def work(self): print("B.work 被调用!!!") pass b = B() b.work() # 请问调用谁? B a = A() a.work() # 请问调用谁? A
-
继承练习:
写一个类Bicycle类, 有run方法,调用时显示骑行里程km class Bicycle: def run(self, km): print("自行车骑行了", km, "公里") 再写一个类EBicycle,在Bicycle类的基础上,添加电池电量volume属性,有两个方法: 1. fill_charge(vol) 用来充电, vol 为电量 2. run(km)方法每骑行10km消耗电量1度,同时显示当前电量,当电量耗尽则,则调用Bicycle的run方法 class EBicyle(Bicycle): ...参考答案
# 答案: # file : bicycle.py class Bicycle: def run(self, km): print("自行车骑行了", km, "公里") class EBicycle(Bicycle): def __init__(self, vol): self.cur_volume = vol # 当前电量 def run(self, km): e_km = min(km, self.cur_volume * 10) # 求km和 乘余电量能行走的最小里程 self.cur_volume -= e_km / 10 if e_km > 0: print("电动车骑行了 %d km" % e_km, "剩余电量:", self.cur_volume) if km > e_km: super().run(km - e_km) def fill_charge(self, vol): print("电动自行车充电", vol, "度") self.cur_volume += vol b = EBicycle(5) # 新买的电动车内有5度电 b.run(10) # 电动骑行了10km 还剩 4度电 b.run(100) # 电动骑行了 40 km ,还剩 0 度电, 用脚登骑行了60km b.fill_charge(10) # 电动自行车充电 10 度 b.run(50) # 骑行了50公里剩余 5度电
5. 封装 enclosure
-
封装是指隐藏类的实现细节,让使用者不用关心这些细节;
-
封装的目的是让使用者通过尽可能少的方法(或属性)操作对象
-
Python的封装是假的(模拟的)封装
-
私有属性和方法
-
python类中以双下划线(
__)开头,不以双下划线结尾的标识符为私有成员,私有成员只能使用方法来进行访问和修改-
以
__开头的属性为类的私有属性,在子类和类外部无法直接使用 -
以
__开头的方法为私有方法,在子类和类外部无法直接调用
-
-
-
私有属性和方法示例:
class A: def __init__(self): self.__p1 = 100 # 私有属性 def __m1(self): # 私有方法 print("__m1(self) 方法被调用") def showA(self): self.__m1() print("self.__p1 = ", self.__p1) class B(A): def __init__(self): super().__init__() def showB(self): self.__m1() # 出错,不允许调用 print("self.__p1 = ", self.__p1) # 出错,不允许调用 # self._A__m1() # 正常调用 # print("self.__p1 =", self._A__p1) # 正常访问 a = A() a.showA() a.__m1() # 出错,不允许调用 v = self.__p1 # 出错,不允许调用 b = B() b.showB() # 访问私有属性 print(a._A__p1) # 输出: 100 # 调用私有方法 a._A__m1() # 输出: __m1(self) 方法被调用 # 不推荐了解就行
class A:
def __init__(self):
self.__a=100#私有属性
self.b=200#实例属性
#私有方法
def __m1(self):
print("私有方法")
a=A()
print(a.b)
#print(a.a) 报错:不能访问私有属性和私有方法
#a.__m1()
print(a._A__a)#强行访问 不推荐
class B(A):
def __init__(self):
A.__init__()
def m2(self):
self.__m1()
print(f"{self.__a}")
b_z=B()
#b_z.m2() 私有方法在内部也不能访问
6. 多态 polymorphic
-
什么是多态:
-
字面意思"多种状态"
-
多态是指在有继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖方法的现象叫多态
-
-
状态:
-
静态(编译时状态)
-
动态(运行时状态)
-
-
多态说明:
-
多态调用的方法与对象相关,不与类型相关
-
Python的全部对象都只有"运行时状态(动态)", 没有"C++语言"里的"编译时状态(静态)"
-
-
多态示例:
class Shape: def draw(self): print("Shape的draw()被调用") class Point(Shape): def draw(self): print("正在画一个点!") class Circle(Point): def draw(self): print("正在画一个圆!") def my_draw(s): s.draw() # 此处显示出多态 shapes1 = Circle() shapes2 = Point() my_draw(shapes1) # 调用Circle 类中的draw my_draw(shapes2) # Point 类中的draw
-
面向对象编程语言的特征:
-
继承
-
封装
-
多态
-
7. 方法重写
果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法
函数重写
-
在自定义类内添加相应的方法,让自定义类创建的实例像内建对象一样进行内建函数操作
对象转字符串函数重写
-
对象转字符串函数重写方法
-
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))
内建函数重写
-
__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(len(myl)) print(abs(myl))
运算符重载
-
运算符重载是指让自定义的类生成的对象(实例)能够使用运算符进行操作
-
运算符重载的作用
-
让自定义类的实例像内建对象一样进行运算符操作
-
让程序简洁易读
-
对自定义对象将运算符赋予新的运算规则
-
-
运算符重载说明:
-
运算符重载方法的参数已经有固定的含义,不建议改变原有的意义
-
算术运算符重载
| 方法名 | 运算符和表达式 | 说明 |
|---|---|---|
__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 | 幂 |
rhs (right hand side) 右手边
-
二元运算符重载方法格式:
def __xxx__(self, other): .... -
算术运算符重载示例
class MyNumber: "此类用于定义一个自定义的类,用于演示运算符重载" def __init__(self, value): "构造函数,初始化MyNumber对象" self.data = value def __str__(self): "转换为表达式字符串" return "MyNumber(%d)" % self.data def __add__(self, rhs): "加号运算符重载" print("__add__ is called") return MyNumber(self.data + rhs.data) def __sub__(self, rhs): "减号运算符重载" print("__sub__ is called") return MyNumber(self.data - rhs.data) n1 = MyNumber(100) n2 = MyNumber(200) print(n1 + n2) print(n1 - n2)
8. super函数
super() 函数是用于调用父类(超类)的一个方法。
super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
super() 方法的语法:
在子类方法中可以使用super().add()调用父类中已被覆盖的方法
可以使用super(Child, obj).myMethod()用子类对象调用父类已被覆盖的方法
class A:
def add(self, x):
y = x+1
print(y)
class B(A):
def add(self, x):
print("子类方法")
super().add(x)
b = B()
b.add(2) # 3
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child()# 子类实例
c.myMethod()# 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
super().init()
super().__init__() 是 Python 中用于调用父类(基类)构造函数的一种方式。它通常用于子类的构造函数中,以确保父类的构造函数被正确调用和初始化。这在继承(inheritance)中尤为重要,因为父类的初始化代码可能包含设置实例变量或执行其他重要的初始化任务。
class Parent:
def __init__(self):
print("Parent class constructor called")
self.parent_attribute = "I am a parent attribute"
class Child(Parent):
def __init__(self):
super().__init__()
print("Child class constructor called")
self.child_attribute = "I am a child attribute"
# 创建一个 Child 类的实例
child_instance = Child()
print(child_instance.parent_attribute)
# 输出
# Parent class constructor called
# Child class constructor called
注释
-
Parent 类:
-
定义了一个构造函数
__init__(),在构造函数中打印了一条消息,并初始化了一个属性parent_attribute。
-
-
Child 类:
-
继承自
Parent类。 -
在其构造函数
__init__()中,首先调用了super().__init__()。这行代码会调用Parent类的构造函数,确保Parent类的初始化逻辑被执行。 -
然后打印了一条消息,并初始化了一个属性
child_attribute。
-
-
实例化 Child 类:
-
创建
Child类的实例时,首先执行Parent类的构造函数,打印 "Parent class constructor called",然后执行Child类的构造函数,打印 "Child class constructor called"。
-
为什么使用 super().__init__()?
-
代码重用:避免在子类中重复父类的初始化代码。
-
正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。
-
支持多重继承:在多重继承情况下,
super()可以确保所有基类的构造函数都被正确调用。
举例
class Attention(nn.Module):
def __init__(self, dim, heads=8, dim_head=64, dropout=0.):
super().__init__()
inner_dim = dim_head * heads # 计算内部维度
project_out = not (heads == 1 and dim_head == dim) # 判断是否需要投影输出
在之后的深度学习方法中经常见 super().__init__()的用法
#解决钻石继承问题
class A:
def process(self):
print("A process")
class B(A):
def process(self):
print("B process")
super().process()#调用super()方法
#A.process(self)#手动调用A的process方法
class C(A):
def process(self):
print("C process")
super().process()
#A.process(self)#手动调用A的process方法 单继承
class D(B,C):
def process(self):
print("D process")
super().process()
#B.process(self)#手动调用A的process方法
#C.process(self)
d=D()
d.process()#钻石继承问题
#super
class Parent:
def __init__(self,name):
self.name=name
class Child(Parent):
def __init__(self,name,age):
super().__init__(name)
self.age=age
def print_info(self):
print(self.name,self.age)
c01=Child("小明",18)
c01.print_info()
Python迭代器与生成器
1. 迭代器 Iterator
什么是迭代器
-
迭代器是访问可迭代对象的工具
-
迭代器是指用 iter(obj) 函数返回的对象(实例)
-
迭代器可以用next(it)函数获取可迭代对象的数据
迭代器函数iter和next
| 函数 | 说明 |
|---|---|
| iter(iterable) | 从可迭代对象中返回一个迭代器,iterable必须是能提供一个迭代器的对象 |
| next(iterator) | 从迭代器iterator中获取下一个记录,如果无法获取一下条记录,则触发 StopIteration 异常 |
迭代器说明
-
迭代器只能往前取值,不会后退
-
用iter函数可以返回一个可迭代对象的迭代器
迭代器示例:
# 示例 可迭代对象
L = [1, 3, 5, 7]
it = iter(L) # 从L对象中获取迭代器
next(it) # 1 从迭代器中提取一个数据
next(it) # 3
next(it) # 5
next(it) # 7
next(it) # StopIteration 异常
# 示例2 生成器函数
It = iter(range(1, 10, 3))
next(It) # 1
next(It) # 4
next(It) # 7
next(It) # StopIteration
迭代器的用途
-
迭代器对象能用next函数获取下一个元素
迭代器函数iter和next 示例:
L = [2, 3, 5, 7]
it = iter(L)
# 访问列表中的所有元素
while True:
try:
print(next(it))
except StopIteration:
print("迭代器访问结束")
break
L = [2, 3, 5, 7]
for x in L:
print(x)
else:
print("迭代器访问结束")
2. 生成器
生成器是在程序运行时生成数据,与容器不同,它通常不会在内存中保留大量的数据,而是现用现生成。
-
yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。
-
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
-
每次使用 yield 语句生产一个值后,函数都将暂停执行,等待被重新唤醒。
-
yield 语句相比于 return 语句,差别就在于 yield 语句返回的是可迭代对象,而 return 返回的为不可迭代对象。
-
然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。
生成器可以用算法动态的生成数据
生成器有两种
-
生成器函数
-
生成器表达式
生成器函数
含有yield 语句的函数是生成器函数,此函数调用回返回一个生成器对象,生成器也是可迭代对象
yield 语句的语法
yield 表达式
生成器函数示例1:
## 定义一个生成器函数, 有 yield 的函数调用后回返回生成器对象
def myrange(stop):
i = 0
while i < stop:
yield i # 为 遍历次生产器的for 语句提供数据
i += 1
for x in myrange(5):
print('x=', x)
# 创建一个生成器对象
gen = myrange(5)
# 使用 next() 函数迭代生成器
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
生成器函数示例2:
def Descendorder(n):
while n > 0:
yield n
n -= 1
# 创建生成器对象
generator = Descendorder(5)
# 通过迭代生成器获取值
print(next(generator))#5
print(next(generator))#4
# 使用 for 循环迭代生成器
for i in generator:
print('for循环:', i)#3 2 1
以上实例中,Descendorder 函数是一个生成器函数。它使用 yield 语句逐步产生从 n 到 1 的倒序数字。在每次调用 yield 语句时,函数会返回当前的倒序数字,并在下一次调用时从上次暂停的地方继续执行。
创建生成器对象并使用 next() 函数或 for 循环迭代生成器,我们可以逐步获取生成器函数产生的值。在这个例子中,我们首先使用 next() 函数获取前两个倒序数字,然后通过 for 循环获取剩下的三个倒序数字。
生成器函数的优势是它们可以按需生成值,避免一次性生成大量数据并占用大量内存。此外,生成器还可以与其他迭代工具(如for循环)无缝配合使用,提供简洁和高效的迭代方式。
生成器表达式
-
语法:
( 表达式 for 变量 in 可迭代对象 [if 真值表达式])
[] 内容代表可以省略
-
作用
用推导式的形式创建一个生成器
-
示例
>>> [x ** 2 for x in range(1, 5)] # 列表解析(列表推导式) [1, 4, 9, 16] >>> >>> (x ** 2 for x in range(1, 5)) # 生成器表达式 <generator object <genexpr> at 0x7f41dcd30a40> >>> for y in (x ** 2 for x in range(1, 5)): ... print(y) ... 1 4 9 16
练习
代码实现斐波那契数列(最少十个数)
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield b
a, b = b, a + b
fib_seq = fibonacci(10)
for i in fib_seq:
print(i, end=" ")
# 运行结果
# 1 1 2 3 5 8 13 21 34 55
#迭代器和生成器
#可迭代的对象
L = list(range(10))
it = iter(L)
print(iter(L))
print(next(it))#依次去拿每一个对象
#异常处理语句
while True:
try:
print(next(it))
except:
print("迭代结束")
break
#生成器
#生成器对象 生成器和迭代器一样可以配套for循环语句使用
def myrange(stop):
i=0
while i<stop:
yield i
i += 1
print(myrange(10))
for x in myrange(10):
print(x)#调用一次产生一个数字
print(next(myrange(10)))
python 函数式编程
定义:用一系列函数解决问题。
-
函数可以赋值给变量,赋值后变量绑定函数。
-
允许将函数作为参数传入另一个函数。
-
允许函数返回一个函数。
1. 函数作为参数
-
将核心逻辑传入方法体,使该方法的适用性更广。
示例1:
def func01():
print("func01执行")
# a = func01
# # print(a)
# a()
def func02():
print("func02执行")
# 通用
def func03(func):
print("func03执行")
func()
func03(func02)
func03(func01)
注释
a = func01:
-
变量
a现在指向func01函数对象。 -
a不是函数的返回值,而是函数对象本身
print(a):
-
打印
a,输出<function func01 at 0x...>,表示a是一个函数对象,并显示其内存地址。
赋值语句 a = func01 并不会执行 func01 函数,只是将函数对象赋值给 a。调用 a() 或 func01() 才会执行函数代码。
示例2:
list01 = [4, 54, 56, 65, 67, 7]
# 需求1:定义函数,在列表中查找所有大于50的数字
def find01():
for item in list01:
if item > 50:
yield item
# 需求2:定义函数,在列表中查找所有小于10的数字
def find02():
for item in list01:
if item < 10:
yield item
# “封装” -- 分
def condition01(item):
return item > 50
def condition02(item):
return item < 10
# 通用
# “继承” - 隔
def find(func):
for item in list01:
# if item < 10:
# if condition02(item):
if func(item):
yield item
# "多态" - 做
for item in find(condition01):
print(item)
for item in find(condition02):
print(item)
总结
1、什么时候使用函数式编程思想?
很多的逻辑或者说核心点是不变的,大多数就是一致的,这个时候我们就可以使用函数式编程思想,可以很好的去定位这个逻辑【函数 式编程思想相对于面向对象编程思想,它更接近于算法】。
2、函数式编程思想替代了面向对象思想?
如果需求中存在多个逻辑变化点时,可以使用类来进行,因为面向对象中存在继承、重写。而函数式编程思想则是将变化点提取到函数 中,实现简单的逻辑。
练习
"""
练习1:在美女列表中,查找颜值大于90的所有美女
练习2:在美女列表中,查找身高小于170的所有美女
"""
class Wife:
def __init__(self, name="", face_score=0, age=0, height=0):
self.name = name
self.face_score = face_score
self.age = age
self.height = height
list_girl = [
Wife("双儿", 96, 22, 166),
Wife("阿珂", 100, 23, 173),
Wife("小郡主", 96, 22, 161),
Wife("方怡", 86, 27, 166),
Wife("苏荃", 99, 31, 176),
Wife("建宁", 93, 24, 163),
Wife("曾柔", 88, 26, 170),
]
def find01():
for item in list_girl:
if item.face_score > 90:
yield item
def find02():
for item in list_girl:
if item.height < 170:
yield item
def condition01(item):
return item.face_score > 90
def condition02(item):
return item.height < 170
def find(func):
for item in list_girl:
# if item.face_score > 90:
# if condition01(item):
if func(item):
yield item
for item in find(condition01):
print(item.name)
for item in find(condition02):
print(item.name)
#python函数式编程
#函数式编程语法 变量存储的是一个内存地址
def fun01():
print("fun01函数执行了")
a=fun01()
print(a)#返回的是内存地址
def fun02(func):
print("fun02执行了")
func()
fun02(fun01)#函数可以作为变量传给另一个函数
#函数编程思想
list01 = list(range(10))
#需求:将大于5的数字取出来
def find01():
for item in list01:
if item >5:
yield item
#需求:查找列表里面的奇数
def find02():
for item in list01:
if item % 2==1:
yield item
#封装 分割 把不同的点分出来
def condition01(item):
return item >5
def condition02(item):
return item % 2 ==1
#继承-隔离 把相同的点融合在一起
def find(func):
for item in list01:
if func(item):
yield item
#做-多态 实现不同的任务
for data in find(condition01):
print(data)
for data in find(condition02):
print(data)
#融合
class Gril:
list_gril = [] # 类属性 存放所有生成的美女信息
def __init__(self,name,score,age,height):
self.name = name
self.score = score
self.age = age
self.height=height
print(self)
# 直接将实例对象存入列表中 目的想以实例属性的方法去访问每个对象
Gril.list_gril.append(self)
# 实例方法 打印所有信息
def print_self(self):
print(self.name,self.age,self.score,self.height)
girl01 = Gril("阿珂", 100, 23,166)
girl02 = Gril("苏荃", 92, 32,154)
girl03 = Gril("双儿", 90, 25,178)
girl04 = Gril("小郡主", 76, 22,168)
girl05 = Gril("曾柔", 67, 24,165)
#需求:查找所有高于160的美女
def fun01():
for item in Gril.list_gril:
if item.height>160:
yield item
#需求:颜值大于90的美女
def fun02():
for item in Gril.list_gril:
if item.score>90:
yield item
#分割
def condition01(item):
return item.height>160
def condition02(item):
return item.score>90
#融合
def find(func):
for item in Gril.list_gril:
if func(item):
yield item
#做 多态
for data in find(condition01):
print(data.name)
for data in find(condition02):
print(data.name)
#函数作为变量传入 封装 继承 多态
1.1.1 lambda 表达式
-
定义:是一种匿名函数
作用:
-- 作为参数传递时语法简洁,优雅,代码可读性强。
-- 随时创建和销毁,减少程序耦合度。
语法
# 定义: 变量 = lambda 形参: 方法体 # 调用: 变量(实参)
说明:
-- 形参没有可以不填
-- 方法体只能有一条语句,且不支持赋值语句。
# 1. 有参数有返回值
# def func01(a,b):
# return a > b
#
# print(func01(10,20))
func01 = lambda a,b:a > b
print(func01(10,20))
# 2. 无参数有返回值
# def func02():
# return "ok"
#
# print(func02())
func02 = lambda :"ok"
print(func02())
# 3. 无参数无返回值
# def func03():
# print("ok")
#
# func03()
func03 = lambda :print("ok")
func03()
# 4. 有参数无返回值
# def func03(a):
# print(a)
#
# func03(100)
func03 = lambda a:print(a)
func03(100)
# 5. lambda 不支持赋值语句
# def func05(iterable):
# iterable[0] = 100
#
# list01 = [1]
# func05(list01)
# print(list01)
# func05 = lambda iterable: iterable[0] = 100 报错
# 6. lambda 不支持多条语句
# def func06(a,b):
# print(a)
# print(b)
#
# func06(10,20)
# func06 = lambda a,b: print(a);print(b)
1.1.2 内置高阶函数
定义:将函数作为参数或返回值的函数。
常用:
(1)map(函数,可迭代对象)
-
使用可迭代对象中的每个元素调用函数,将返回值作为新可迭代对象元素;返回值为新可迭代对象。
(2)filter(函数,可迭代对象)
-
根据条件筛选可迭代对象中的元素,返回值为新可迭代对象。
(3)sorted(可迭代对象, key=函数, reverse=True)
-
排序,返回值为排序后的列表结果。 True是降序排序。
(4)max(可迭代对象, key = 函数)
-
根据函数获取可迭代对象的最大值。
(5)min(可迭代对象,key = 函数)
-
根据函数获取可迭代对象的最小值。
示例
class Girl:
def __init__(self, name="", face_score=0, age=0, height=0):
self.name = name
self.face_score = face_score
self.age = age
self.height = height
def __str__(self):
return "%s-%d-%d-%d" % (self.name, self.face_score, self.age, self.height)
list_girl = [
Girl("双儿", 96, 22, 166),
Girl("阿珂", 100, 23, 173),
Girl("小郡主", 96, 22, 161),
Girl("方怡", 86, 27, 166),
Girl("苏荃", 99, 31, 176),
Girl("建宁", 93, 24, 163),
Girl("曾柔", 88, 26, 170),
]
# 1. map 映射
# 在美女列表中获取所有名称
# 类似于:select
for element in map(lambda item: item.name, list_girl):
print(element)
# 2. filter 过滤器
# 在美女列表中获取颜值大于90的所有美女
# 类似于:find_all
for element in filter(lambda item: item.face_score > 90, list_girl):
print(element)
# 3. max/min
# 在美女列表中获取颜值最高的美女
# 类似于:get_max
print(max(list_girl,key = lambda item:item.face_score))
print(min(list_girl,key = lambda item:item.face_score))
# 4.sorted 排序
# 注意:没有改变原有列表,而是返回新的
# 升序
for item in sorted(list_girl,key = lambda item:item.height):
print(item)
# 降序
for item in sorted(list_girl,key = lambda item:item.height,reverse=True):
print(item)
#lambda表达式
#高阶内置函数
class Gril:
list_gril = [] # 类属性 存放所有生成的美女信息
def __init__(self,name,score,age,height):
self.name = name
self.score = score
self.age = age
self.height=height
print(self)
# 直接将实例对象存入列表中 目的想以实例属性的方法去访问每个对象
Gril.list_gril.append(self)
# 实例方法 打印所有信息
def print_self(self):
print(self.name,self.age,self.score,self.height)
girl01 = Gril("阿珂", 100, 23,166)
girl02 = Gril("苏荃", 92, 32,154)
girl03 = Gril("双儿", 90, 25,178)
girl04 = Gril("小郡主", 76, 22,168)
girl05 = Gril("曾柔", 67, 24,165)
print(girl04)
def __str__(self):
#必须以字符串的形式返回
return f"{self.name}-{self.score}-{self.age}-{self.height}"
#1.打印所有对象的名称
for element in map(lambda item:item.name,Gril.list_gril):
print(element)#map函数
#2,获取所有颜值大于80的美女对象
for element in filter(lambda item:item.score>80,Gril.list_gril):
print(element)
#3.获取年龄最大的美女对象
max_gril= max(Gril.list_gril,key=lambda item:item.age)
print(max_gril)
#4.sorted 排序 颜值升序排列
for element in sorted(Gril.list_gril,key=lambda item:item.score):
print(element)
2. 函数作为返回值
2.1 闭包 closure
什么是闭包?
-
闭包是指引用了此函数外部嵌套函数的变量的函数 闭包就是能够读取其他函数内部变量的函数。只有函数内部的嵌套函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数,同时这个函数又引用了外部的变量“。
-
在本质上,闭包是将内部嵌套函数和函数外部的执行环境绑定在一起的对象。
闭包必须满足以下三个条件:
-
必须有一个内嵌函数
-
内嵌函数必须引用外部函数中变量
-
外部函数返回值必须是内嵌函数。
思考全局变量和局部变量的区别
-
全局变量
-
一直存在,谁都可以访问和修改
-
-
局部变量
-
只是在调用时存在,只能在函数内部进行访问和修改
-
闭包的优缺点 优点
1. 逻辑连续,当闭包作为另一个函数调用参数时,避免脱离当前逻辑而单独编写额外逻辑。 2. 方便调用上下文的局部变量。 3. 加强封装性,是第2点的延伸,可以达到对变量的保护作用。
使用闭包的注意点(缺点)
1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包 2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
示例1
def give_yasuiqian(money):
def child_buy(obj, m):
nonlocal money
if money > m:
money -= m
print('买', obj, '花了', m, '元, 剩余', money, '元')
else:
print("买", obj, '失败')
return child_buy
cb = give_yashuqian(1000)
cb('变形金刚', 200)
cb('漫画三国', 100)
cb('手机', 1300)
示例2
# file : closure.py
def make_power(y):
def fn(x):
return x ** y
return fn
pow2 = make_power(2)
print("5的平方是:", pow2(5))
pow3 = make_power(3)
print("6的立方是:", pow3(6))
#闭包:三大要素:具有内外函数(嵌套函数),内部函数访问外部函数的变量,外部函数要返回内部函数
#闭包创建
def fun01():
a = 1
def fun02():
print(a)
return fun02()#或者 re = fun01() re() print(re)
re = fun01()
#闭包应用:逻辑连续,装饰器打基础
#体现:外部函数执行过后,栈帧不释放,等待内部函数使用
def give_money(money):
print(f"当前有{money}")
def child_buy(target,price):
nonlocal money
money -=price
print(f"购买了{target},剩余{money}")
return child_buy
action=give_money(100)
action("apple",20)
action("orange", 20)
2.2 装饰器 decorators(专业提高篇)
什么是装饰器
-
装饰器是一个函数,主要作用是来用包装另一个函数或类
装饰器的作用:
-
在不修改被装饰的函数的源代码,不改变被装饰的函数的调用方式的情况下添加或改变原函数的功能。
函数装饰器的语法:
def 装饰器函数名(fn):
语句块
return 函数对象
@装饰器函数名 <换行>
def 被装饰函数名(形参列表):
语句块
用函数装饰器替换原函数myfun
def mydeco(fn):
fn()
print("装饰器函数被调用了,并返回了fx")
def fx():
print("fx被调用了")
# return fn()
return fx
@ mydeco
def myfun():
print("函数myfun被调用")
myfun()
myfun()
当使用@mydeco语法装饰myfun函数时,实际上发生的是:
-
myfun函数作为参数传递给了mydeco装饰器。 -
在
mydeco内部,首先调用了fn(),即此时调用了myfun函数,产生了输出:"函数myfun被调用"。 -
接着,打印了"装饰器函数被调用了,并返回了fx"。
-
然后,
mydeco装饰器返回了新的函数fx。
因此,此刻myfun实际上被替换成了新的函数fx。这样的行为正是Python装饰器的特性之一:装饰器可以修改函数的行为,甚至完全替换被装饰的函数。
2.2.1 基本装饰器
有参数的函数装饰器(在myfunc外加了一层)
def mydeco(fn):
def fx():
print("====这是myfunc被调用之前====")
ret = fn()
print("----这是myfunc被调用之后====")
return ret
return fx
@mydeco
def myfunc():
print("myfunc被调用.")
myfunc()
myfunc()
myfunc()
2.2.2 带参数的装饰器
def repeat(num):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3) # 应用装饰器,重复执行下面的函数3次
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # 调用被装饰的函数
-
repeat是一个接受参数的装饰器工厂函数,它返回一个装饰器。 -
decorator是真正的装饰器,它接受一个函数func作为参数。 -
wrapper函数重复执行被装饰的函数num次。 -
使用
@repeat(3)应用装饰器,使greet函数被执行3次。
注意:带参数的装饰器需要三层函数,def wrapper(args, kwargs) 传入的是被修饰的函数的参数。
2.2.3 装饰器链
def uppercase(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
def exclamation(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result + "!"
return wrapper
@exclamation
@uppercase
def say_hello(name):
return f"Hello, {name}"
greeting = say_hello("Bob")
print(greeting) # 输出 "HELLO, BOB!"
具体的执行过程如下:
-
装饰器是从内到外依次应用的。在你的代码中,首先应用的是
@uppercase,然后是@exclamation。 -
@uppercase装饰器会先包裹say_hello函数,然后@exclamation装饰器再包裹已经被@uppercase包裹过的函数。
步骤详细如下:
-
首先
@uppercase包装say_hello函数:-
调用
say_hello("Bob")返回"Hello, Bob"。 -
@uppercase装饰器的wrapper函数将结果转换为大写,变为"HELLO, BOB"。
-
-
然后
@exclamation包装已经被@uppercase包装过的say_hello函数:-
调用
wrapper(即@uppercase装饰器的wrapper函数)返回"HELLO, BOB"。 -
@exclamation装饰器的wrapper函数将结果加上一个感叹号,变为"HELLO, BOB!"。
-
say_hello("Bob") -> "Hello, Bob"
"Hello, Bob" -> @uppercase -> "HELLO, BOB"
"HELLO, BOB" -> @exclamation -> "HELLO, BOB!"
2.2.4 类装饰器
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Something is happening before the function is called.")
result = self.func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
@MyDecorator # 应用类装饰器
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Charlie") # 调用被装饰的函数
-
MyDecorator是一个类装饰器,它接受一个函数func作为参数并在__call__方法中执行额外操作。 -
使用
@MyDecorator应用类装饰器,它将包装say_hello方法,使其在调用前后执行额外操作。 -
与基本装饰器类似
-
#装饰器 #用闭包过渡装饰器 def fun01(): print("fun01执行了") return "hello world" def fun02(func): print("fun02执行了") def print_func_name(func): def wrapper(*args,**kwargs): #旧功能 #__name__获取函数名 内置属性 print(func.__name__) #新功能 return func(*args,**kwargs) return wrapper re=print_func_name(fun01) re() #装饰器 #装饰器名称 def print_func_name(func): def wrapper(*args,**kwargs): #旧功能 #__name__获取函数名 内置属性 print(func.__name__) #新功能 return func(*args,**kwargs) return wrapper #被装饰器函数 @print_func_name def fun01(): print("fun01执行了") return "hello world" @print_func_name def fun02(): print("fun02执行了") fun02() fun01() #装饰器应用 #旧函数:统计值 #新函数;功能是打印总执行时间 import time #新函数 def all_time(func): def wrapper(*args,**kwargs): #time.time()方法可以获取当前时间 start_time=time.time() #调用旧函数 result=func(*args,**kwargs) #获得结束时间 end_time=time.time() print("执行时间:",end_time-start_time) return result return wrapper #旧函数 @all_time def fun01(): sum_value = 0 for i in range(100000000): sum_value+=i return sum_value #fun01() @all_time def fun02(n): sum_value = 0 for i in range(n): sum_value+=i return sum_value fun02(10000000)
1812

被折叠的 条评论
为什么被折叠?



