挑战全栈第八天!今天更新Python中的迭代器和生成器,以及函数式编程的内容。
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) # 判断是否需要投影输出
什么是迭代器
迭代器是访问可迭代对象的工具
迭代器是指用 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("迭代器访问结束")
for x in L 的工作原理
当使用 for x in L 时,Python 内部会执行以下步骤:
获取迭代器:调用 L.__iter__()
,获取一个迭代器对象。
迭代元素:通过迭代器的 __next__()
方法逐个获取元素,直到抛出 StopIteration
异常。
赋值和循环:将每次获取的元素赋值给变量 x
,并执行循环体。
因此,for x in L 本身并不是迭代器,而是利用了迭代器的机制来实现循环。
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current < self.end:
self.current += 1
return self.current - 1
else:
raise StopIteration
# 使用迭代器
my_iter = MyIterator(1, 5)
for num in my_iter:
print(num)
生成器是在程序运行时生成数据,与容器不同,它通常不会在内存中保留大量的数据,而是现用现生成。
yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
每次使用 yield 语句生产一个值后,函数都将暂停执行,等待被重新唤醒。
yield 语句相比于 return 语句,差别就在于 yield 语句返回的是可迭代对象,而 return 返回的为不可迭代对象。
然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。
生成器可以用算法动态的生成数据
主要特点:
惰性求值:生成器不会一次性计算所有值,而是逐个生成值,节省内存。
yield
语句:生成器通过 yield
语句返回值,每次调用 next()
时,生成器会从上次 yield
的位置继续执行。
自动实现迭代器协议:生成器自动实现了 __iter__()
和 __next__()
方法,因此可以直接用于 for
循环或其他迭代工具。
生成器有两种
生成器函数
生成器表达式
生成器函数
生成器函数通过 yield
语句返回值。每次调用 next()
时,生成器会从上次 yield
的位置继续执行,直到遇到下一个 yield
或抛出 StopIteration
异常。
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)) # 生成器表达式
at 0x7f41dcd30a40>
>>> for y in (x ** 2 for x in range(1, 5)):
... print(y)
...
1
4
9
16
函数式编程(Functional Programming,FP)是一种编程范式,它将计算视为数学函数的求值,并强调使用纯函数(Pure Functions)和不可变数据(Immutable Data)。函数式编程的核心思想是将程序分解为一系列可组合的函数,通过函数的组合来实现复杂的逻辑。
在函数式编程中,函数被视为一等公民(First-Class Citizens),可以像普通变量一样被传递、返回或存储。
函数式编程的核心在于三个基本原则:纯函数、不可变数据和高阶函数。
纯函数是指 对于相同的输入,总是返回相同的输出,并且没有副作用 的函数。副作用是指函数修改了函数外部的状态,例如修改全局变量、写入文件等。
纯函数的优点:
可测试性: 纯函数易于测试,因为它们的输出只取决于输入,不受外部状态的影响。
可缓存性: 纯函数的结果可以缓存,因为相同的输入总是产生相同的输出。
可并行性: 纯函数可以安全地并行执行,因为它们没有副作用,不会相互干扰。
# 纯函数示例
def sum(a, b):
return a + b
# 非纯函数示例
total = 0
def add_to_total(x):
global total
total += x
return total
sum
函数是纯函数,因为它不依赖于任何外部状态,并且对于相同的输入总是返回相同的输出。而 add_to_total
函数不是纯函数,因为它修改了全局变量 total
,产生了副作用。
不可变数据是指数据一旦创建就不能被修改。任何对数据的操作都会返回一个新的数据,而不是修改原数据。
Python 中的不可变数据类型包括:整数、字符串、元组等。
使用不可变数据可以提高代码的可靠性,因为它可以防止数据被意外修改。
# 不可变数据示例
coordinates = (10, 20) # 元组
colors = frozenset(['red', 'green', 'blue']) # frozenset
# 可变数据示例
names = ['Alice', 'Bob']
names.append('Charlie') # 列表可以被修改
高阶函数是指可以接受函数作为参数或返回函数作为结果的函数。
高阶函数是函数式编程的核心特性之一,它允许函数之间的组合和抽象。
(1)变量可以指向函数
以 Python 内置的求绝对值的函数abs()
为例,调用该函数用以下代码:
print(abs(-10)) # 输出:10
print(abs)
abs()是函数的调用,而abs是函数本身。
如果把函数本身赋值给变量呢?
# 函数本身赋给变量,即f指向abs所指向的函数
f = abs
a = f(-10)
print(a)
说明变量f
现在已经指向了abs
函数本身。直接调用abs()
函数和调用变量f()
完全相同。
(2) 函数名也是变量
函数名其实就是指向函数的变量!对于abs()
这个函数,完全可以把函数名abs
看成变量,它指向一个可以计算绝对值的函数!
abs=10
print(abs(-10))
# TypeError: 'int' object is not callable
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
示例:
def add(x, y, f):
return f(x) + f(y)
print(add(-5,6,abs))
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
定义:将函数作为参数或返回值的函数。
常用:
(1)map(函数,可迭代对象)
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
(2)reduce(函数,可迭代对象)
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
(2)filter(函数,可迭代对象)
根据条件筛选可迭代对象中的元素,返回值为新可迭代对象。
(3)sorted(可迭代对象, key=函数, reverse=True)
排序,返回值为排序后的列表结果。
(4)max(可迭代对象, key = 函数)
根据函数获取可迭代对象的最大值。
(5)min(可迭代对象,key = 函数)
根据函数获取可迭代对象的最小值。
示例
# 将列表中的元素值平方
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r))
# 把序列[1, 3, 5, 7, 9]变换成整数13579
from functools import reduce
def fn(x, y):
return x * 10 + y
result = reduce(fn, [1, 3, 5, 7, 9]) # 13579
print(result)
# 在一个 list 中,删掉偶数,只保留奇数
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
# 按绝对值大小排序
sorted([36, 5, -12, 9, -21], key=abs)
# 求列表中的最大元素值
numbers = [3, 1, 4, 1, 5, 9, 2]
print(max(numbers)) # 输出: 9
# 求列表中单词长度最大的元素
words = ["apple", "banana", "cherry"]
longest_word = max(words, key=len)
print(longest_word) # 输出: "banana"
定义:是一种匿名函数
作用:
-- 作为参数传递时语法简洁,优雅,代码可读性强。
-- 随时创建和销毁,减少程序耦合度。
语法
# 定义:
变量 = lambda 形参: 方法体
# 调用:
变量(实参)
说明:
-- 形参没有可以不填
-- 方法体只能有一条语句,且不支持赋值语句。
# 将列表中的元素值平方
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r))
# 改造为lambda
r= map(lambda x:x * x,[1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r))
# 把序列[1, 3, 5, 7, 9]变换成整数13579
from functools import reduce
result = reduce(lambda x, y: 10 * x + y, [1, 3, 5, 7, 9]) # 13579
print(result)
什么是闭包?
闭包是指引用了此函数外部嵌套函数的变量的函数 闭包就是能够读取其他函数内部变量的函数。只有函数内部的嵌套函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数,同时这个函数又引用了外部的变量“。
在本质上,闭包是将内部嵌套函数和函数外部的执行环境绑定在一起的对象。
闭包必须满足以下三个条件:
必须有一个内嵌函数
内嵌函数必须引用外部函数中变量
外部函数返回值必须是内嵌函数。
示例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))
闭包可以捕获和修改外部变量,可能导致副作用。
示例3:假设有两个方法:add(x,y)和mul(x,y),使用闭包实现,既可以调用add,也可以调用mul方法
def outer(func):
def inner(x,y):
return func(x,y)
return inner
def add(x,y):
return x + y
def mul(x,y):
return x * y
inner = outer(add)
print(inner(1,2))
inner = outer(mul)
print(inner(1,2))
闭包的优缺点
优点
提供数据封装,避免全局变量污染。
支持函数式编程的特性,如高阶函数和柯里化。
可以创建私有变量和方法。
缺点
可能导致内存泄漏,因为闭包会保留外部函数的变量引用,无法被垃圾回收。
过度使用闭包可能使代码难以理解和调试。
什么是装饰器
装饰器是一个函数,主要作用是来用包装另一个函数或类
装饰器的作用:
在不修改被装饰的函数的源代码,不改变被装饰的函数的调用方式的情况下添加或改变原函数的功能。
函数装饰器的语法:
def 装饰器函数名(fn):
语句块
return 函数对象
@装饰器函数名 <换行>
def 被装饰函数名(形参列表):
语句块
# 日志打印函数
def appendLog(func):
print(f"{func.__name__}打印日志")
# 闭包
def funcOut(func):
def funcIn():
appendLog(func)
func()
return funcIn
# 闭包函数名作为装饰器
@funcOut
def func1():
print("func1正在运行...")
@funcOut
def func2():
print("func2正在运行...")
# 添加装饰器后,不需要显式调用闭包的外函数
# func1 = funcOut(func1)
func1()
# func2 = funcOut(func2)
func2()
当使用@funcOut
语法装饰func1
函数时,实际上发生的是:
func1
函数作为参数传递给了funcOut
装饰器。
在funcOut
内部,定义了funcIn,在funcIn内部首先调用了appendLog()
,然后调用func(),即此时调用了func1
函数
接着,返回内部函数名funcIn。
然后,funcOut
装饰器将内部函数名赋值给变量func1,当执行func1时则执行内部函数funcIn()。
假设有一个函数:
def sum(a,b):
return a+b
使用基本装饰器实现:
def outer(func):
def inner():
return func()
return inner
@outer
def sum(a,b):
return a+b
print(sum(1,2))
# 报错:TypeError: inner() takes 0 positional arguments but 2 were given
修改代码,给内部函数添加参数:
def outer(func):
def inner(x,y):
return func(x,y)
return inner
@outer
def sum(a,b):
return a+b
print(sum(1,2))
# 输出3
如果再添加一个add函数:
def add(a, b, c):
return a + b + c
那么还需要定义一个闭包来作为装饰器:
def outer1(func):
def inner(x,y,z):
return func(x,y,z)
return inner
@outer1
def add(a, b, c):
return a + b + c
print(add(1,2,3))
从上边的代码来看,两个参数相加,三个参数相加都要相应的添加对应的装饰器,能定义一个通用的装饰器吗?
可以考虑将闭包的内部函数中的参数换成*args
def outer(func):
def inner(*args):
return func(*args)
return inner
@outer
def add(a, b, c):
return a + b + c
@outer
def sum(a,b):
return a+b
print(add(1,2,3))
print(sum(1,2))
为了后续方便扩展,在内部方法中再添加**kwargs:
def outer(func):
def inner(*args, **kwargs):
return func(*args,**kwargs)
return inner
至此带参数装饰器完成。
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
包裹过的函数。
即等价于:
say_hello = exclamation(uppercase(say_hello))
步骤详细如下:
首先 @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!"
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
方法,使其在调用前后执行额外操作。
与基本装饰器类似