迭代器在Python中无处不在。它们在for循环,理解,生成器等中优雅地实现,但却隐藏在眼皮底下。
Python中的Iterator只是一个可以迭代的对象。一个将返回数据的对象,一次返回一个元素。
从技术上讲,Python 迭代器对象必须实现两个特殊方法,iter()和__next__()统称为迭代器协议。
如果我们可以从对象获得迭代器,则该对象称为可迭代。Python中的大多数内置容器(例如:list,tuple,string等)都是可迭代的。
iter()函数(也就是__iter__()方法)从它们返回一个迭代器。
1示例
my_list = [4, 7, 0, 3] # 使用iter()获得迭代器 my_iter = iter(my_list) #输出 4 print(next(my_iter)) #输出 7 print(next(my_iter)) ## next(obj)与obj .__ next __()相同 #输出 0 print(my_iter.__next__())2for循环 在正常编码中,我们常常使用for循环来迭代
for element in iterable: # 对元素做点什么其真实 for 循环 内部代码为
# 创建一个迭代器对象iterable iter_obj = iter(iterable) # 无限循环 while True: try: # 获取下一项 element = next(iter_obj) # 对元素做点什么 except StopIteration: # 如果引发StopIteration,则从循环中断 break因此,在内部,for循环通过在iterable上调用iter()创建一个迭代器对象iter_obj。
具有讽刺意味的是,这个for循环实际上是一个无限的while循环。
在循环内部,它调用next()来获取下一个元素,并使用这个值执行for循环的主体。当所有的项都用完后,StopIteration被抛出,它在内部被捕获,循环结束。注意,任何其他类型的异常都会通过。
3构建自己的迭代器 在Python中从头开始构建迭代器很容易。我们只需要实现这些方法__iter__()和__next__()。
iter()方法返回迭代器对象本身。如果需要,可以执行一些初始化。
next()方法必须返回序列中的下一项。在到达终点时,以及在随后的调用中,它必须引发StopIteration。
这里,我们展示了一个示例,它将在每次迭代中为我们提供2的次幂。幂指数从0到用户设置的数字。
class PowTwo: """实现迭代器的类 二的幂""" def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n <= self.max: result = 2 ** self.n self.n += 1 return result else: raise StopIteration a = PowTwo(4) print(a.__iter__()) print(a.__next__()) print(a.__next__()) print(a.__next__()) print(a.__next__()) print(a.__next__()) print(a.__next__())1什么是生成器 用Python构建迭代器有很多开销; 我们必须使用__iter__()和__next__()方法实现一个类,跟踪内部状态,在没有要返回的值时触发StopIteration等等。
这既冗长又违反直觉。生成器在这种情况下可以派上用场。
Python生成器是创建迭代器的简单方法。我们上面提到的所有开销都由Python的生成器自动处理。
简而言之,生成器是一个函数,它返回一个对象(迭代器),我们可以对其进行迭代(一次一个值)。
2如何创建生成器 在Python中创建生成器非常简单。 就像使用yield语句而不是return语句定义普通函数一样容易。
如果一个函数包含至少一个yield语句(它可能包含其他yield或return语句),那么它就成为一个生成器函数。yield和return都将从函数返回一些值。
不同之处在于,当return语句完全终止一个函数时,yield语句会暂停该函数保存其所有状态,然后在后续调用时继续执行。
3生成器函数和普通函数区别 生成器函数包含一个或多个yield语句。 调用时,它返回一个对象(迭代器),但不会立即开始执行。 像__iter__()和__next__()这样的方法会自动实现。因此,我们可以使用next()来遍历项目。 一旦函数产生了结果,函数就会暂停,控制就会转移给调用者。 局部变量及其状态在连续调用之间被记住。 最后,当函数终止时,在进一步调用时会自动引发StopIteration。
4代码
# 一个简单的生成器函数 def my_gen(): n = 1 print('这是第一次打印') # 生成器函数包含yield语句 yield n n += 1 print('这是第二次打印') yield n n += 1 print('这是最后一次打印') yield n a=my_gen() print(a.__next__()) print(a.__next__()) print(next(a)) print(next(a))5生成器表达式 使用生成器表达式可以轻松地动态创建简单的生成器。它使建造生成器变得容易。 与lambda函数创建匿名函数相同,生成器表达式创建匿名生成器函数。 生成器表达式的语法类似于Python中的列表理解语法。但是将方括号替换为圆括号。 列表理解与生成器表达式之间的主要区别在于,虽然列表理解生成整个列表,但生成器表达式一次生成一个项目。 他们有点懒,只在需要时才生成项目。由于这个原因,生成器表达式比等价的列表理解的内存效率要高得多。
# 初始化列表 my_list = [1, 3, 6, 10] # 使用列表理解对每个项目进行平方 # 输出: [1, 9, 36, 100] print('[x**2 for x in my_list]:',[x**2 for x in my_list]) # 同样的事情可以使用生成器表达式来完成 # 输出: <generator object <genexpr> at 0x0000000002EBDAF8> a=(x**2 for x in my_list) print('a:',a) print('next(a):',next(a))1嵌套函数中的非局部变量 在了解闭包是什么之前,我们必须首先了解什么是嵌套函数和非局部变量。 在另一个函数内部定义的函数称为嵌套函数。嵌套函数可以访问封闭范围的变量。 在Python中,默认情况下,这些非本地变量是只读的,并且我们必须将它们明确声明为非本地变量(使用nonlocal关键字)才能进行修改。 以下是访问非局部变量的嵌套函数的示例。
def print_msg(msg): # 这是外部封闭函数 def printer(): # 这是嵌套函数 print(msg) printer() # 我们执行这个函数 # 输出: Hello print_msg("Hello")2定义闭包函数
在上面的示例中,如果函数print_msg()的最后一行返回了printer()函数而不是调用它,将会发生什么? 这意味着功能定义如下。
def print_msg(msg): # 这是外部封闭函数 def printer(): # 这是嵌套函数 print(msg) return printer # 这变了 # 现在,让我们尝试调用此函数。 # 输出: Hello another = print_msg("Hello") another()这很不寻常。 print_msg()函数用字符串调用,"Hello"返回的函数绑定到另一个名称。在调用时another(),尽管我们已经完成了print_msg()函数的执行,但仍然记得该消息。 这种将一些数据(“Hello”)附加到代码上的技术在Python中称为闭包。 即使变量超出范围或函数本身已从当前命名空间中删除,也会记住封闭范围中的这个值。
3闭包的条件 从上面的实例可以看出,在Python中,当嵌套的函数在其封闭的范围内引用一个值时,我们有一个闭包。 以下几点总结了在Python中创建闭包必须满足的条件。
我们必须有一个嵌套函数(函数在函数内部)。嵌套函数必须引用在封闭函数中定义的值。封闭函数必须返回嵌套函数。4何时使用闭包 那么,闭包有什么用呢? 闭包可以避免使用全局值,并提供某种形式的数据隐藏。它还可以为该问题提供面向对象的解决方案。 当在一个类中实现的方法很少(大多数情况下是一个方法)时,闭包可以提供另一种更优雅的解决方案。但是,当属性和方法的数量变大时,最好实现一个类。 这是一个简单的示例,其中闭包可能比定义类和创建对象更可取。
def make_multiplier_of(n): def multiplier(x): return x * n return multiplier # 3的乘数 times3 = make_multiplier_of(3) # 5的乘数 times5 = make_multiplier_of(5) # 输出: 27 print(times3(9))#此时x=9 # 输出: 15 print(times5(3))#此时x=3 # 输出: 30 print(times5(times3(2)))#times3(2)=6,x=times3(2)1.什么是装饰器 装饰器接受一个函数,添加一些功能并返回它。 Python有一个有趣的功能,称为装饰器,可将功能添加到现有代码中。 这也称为元编程,因为程序的一部分试图在编译时修改程序的另一部分。
2学习装饰器的先决条件 为了了解装饰器,我们必须首先了解Python的一些基本知识。 我们必须接受这样一个事实,即Python中的所有内容都是对象。我们定义的名称只是绑定到这些对象的标识符。函数也不例外,它们也是对象(带有属性)。可以将各种不同的名称绑定到同一功能对象。 这是一个实例。
def first(msg): print(msg) first("Hello") second = first second("Hello")当您运行代码时,这两个函数first和second给出相同的输出。在此,名称first和second指代相同的功能对象。 现在情况是不是感觉变复杂了点,可以将函数作为参数传递给另一个函数。 如果您在Python中使用过map,filter和reduce之类的函数,那么您已经知道这一点。 这种以其他函数为参数的函数也称为高阶函数。这是这种函数的一个实例。
def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result operate(inc,3)#4 operate(dec,3)#2此外,一个函数可以返回另一个函数。 即闭包
def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() #输出 "Hello" new()3回到装饰器 函数和方法被称为可调用的,因为它们可以被调用。 实际上,任何实现特殊方法__call __()的对象都称为可调用的。 因此,从最基本的意义上讲,装饰器是可调用的,可返回可调用的。 基本上,装饰器接受一个函数,添加一些功能并返回它。
def make_pretty(func): def inner(): print("我被装饰了") func() return inner def ordinary(): print("我是普通的函数") ordinary() print('.....') pretty = make_pretty(ordinary) pretty()在上面显示的示例中,make_pretty()是一个装饰器。在分配步骤中。
pretty = make_pretty(ordinary)
函数ordinary()被修饰,返回的函数被命名为pretty。 我们可以看到decorator函数在原来的函数中添加了一些新功能。这类似于包装礼物。装饰器充当包装器。被装饰的物品(里面的礼物)的性质不会改变。但是现在,它看起来很漂亮(自从装饰之后)。 通常,我们装饰一个函数并将其重新分配为
ordinary = make_pretty(ordinary).
这是一个常见的构造,因此,Python具有简化此语法的语法。 我们可以将@符号与装饰器函数的名称一起使用,并将其放置在要装饰的函数的定义上方。例如,
def make_pretty(func): def inner(): print("我被装饰了") func() return inner @make_pretty def ordinary(): print("我是普通的函数") ordinary()相当于
def ordinary(): print("我是普通的函数") ordinary = make_pretty(ordinary)4.带参数的装饰器 装饰器里有很多闭包知识
def smart_divide(func): def inner(a,b): print("我要做除法",a,"和",b) if b == 0: print("哎呀!不能除") return return func(a,b) return inner @smart_divide def divide(a,b): return a/b print(divide(2,5))通过这种方式,我们可以装饰带有参数的函数。 敏锐的观察者会注意到,inner()装饰器内部的嵌套函数的参数与其装饰的函数的参数相同。考虑到这一点,现在我们可以使通用装饰器可以使用任意数量的参数。 在Python中,此魔术是通过完成的function(*args, **kwargs)。这样,args是位置参数的元组,kwargs而是关键字参数的字典。这样的装饰器的一个实例是。
def works_for_all(func): def inner(*args, **kwargs): print("我可以装饰任何函数") return func(*args, **kwargs) return inner5.链接装饰器 可以在Python中链接多个装饰器。 这就是说,一个函数可以用不同(或相同)的装饰器多次装饰。我们只需将装饰器放置在所需函数之上。
def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")在定义和详细了解@property是什么之前,让我们了解为什么首先需要使用它。、 一个实例开始 假设您决定创建一个以摄氏度为单位存储温度的类。它还将实现一种将温度转换为华氏温度的方法。其中一种方法如下。
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # 创建新对象 man = Celsius() # 设定温度 man.temperature = 37 # 获得华氏度 print(man.to_fahrenheit())如上所示,每当我们分配或检索任何对象属性(如temperature)时,Python都会在对象的__dict__字典中进行搜索。
man.dict {‘temperature’: 37}
现在,让我们进一步假设我们的课程在客户中很受欢迎,并且他们开始在程序中使用它。 他们对对象进行了各种分配。
有一天,一个值得信赖的客户来找我们,建议温度不能低于-273摄氏度(热力学专业的学生可能会说实际上是-273.15摄氏度),也被称为绝对零度。他进一步要求我们实现这个值约束。作为一家追求客户满意度的公司,我们很高兴地听取了这个建议,并发布了1.01版本(对现有类的升级)。
1 使用getter和setter 解决上述约束的一个明显方法是隐藏属性temperature(将其设为私有),并定义新的getter和setter接口以对其进行操作。这可以如下进行。
class Celsius: def __init__(self, temperature = 0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # new update def get_temperature(self): return self._temperature def set_temperature(self, value): if value < -273: raise ValueError("-273度是不可能的") self._temperature = value我们在上面可以看到get_temperature(),set_temperature()已经定义了新方法,此外,用_temperature替换了temperature。下划线(_)开头表示Python中的私有变量。
c = Celsius(-277) Traceback (most recent call last):
ValueError: Temperature below -273 is not possible
c = Celsius(37) c.get_temperature() 37 c.set_temperature(10) c.set_temperature(-300) Traceback (most recent call last): ValueError: Temperature below -273 is not possible
但这不是一个大问题。上述更新的最大问题在于,所有在程序中实现了上一类的客户端都必须将其代码从obj.temperature修改为obj.get_temperature(),并将所有分配(例如obj.temperature = val修改为obj.set_temperature( val))。
这种重构会给客户带来数十万行代码的麻烦。
总而言之,我们的新更新不向后兼容。这是@property发挥作用的地方。
2@property的力量 python处理上述问题的方法是使用property。我们可以这样来实现它。
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 def get_temperature(self): print("获得的值") return self._temperature def set_temperature(self, value): if value < -273: raise ValueError("零下273度是不可能的") print("设定值") self._temperature = value temperature = property(get_temperature,set_temperature) c = Celsius()同样,任何访问如c.temperature都会自动调用get_temperature()。 这就是属性的作用。 这里还有一些实例。
同样,任何设定.temperature都会自动调用set_temperature()。 这就是属性的作用。
任何检索温度值的代码都将自动调用get_temperature()而不是字典(dict)查找。 同样,任何为温度分配值的代码都会自动调用set_temperature()。 这是Python中的一项很酷的功能。 我们可以在上面看到即使创建对象时也会调用set_temperature()。
3深入了解property 在Python中,property()是一个内置函数,用于创建并返回属性对象。该函数的签名是
property(fget=None, fset=None, fdel=None, doc=None)
其中,fget为获取属性值的函数,fset为设置属性值的函数,fdel为删除属性的函数,doc为字符串(如注释)。从实现中可以看出,这些函数参数是可选的。因此,可以简单地按照以下方式创建属性对象。
属性对象有三个方法,getter()、setter()和deleter(),用于稍后指定fget、fset和fdel。这意味着
temperature = property(get_temperature,set_temperature)
也可以分解为
创建空属性 temperature = property() 设置 fget temperature = temperature.getter(get_temperature) #设置 fset temperature = temperature.setter(set_temperature)
熟悉Python中装饰器的程序员可以认识到上述构造可以实现为装饰器。
我们可以更进一步,不定义名称get_temperature,set_temperature,因为它们是不必要的,并且会影响类命名空间。为此,我们在定义getter和setter函数时重用了名称temperature。这是可以做到的。
class Celsius: def __init__(self, temperature = 0): self._temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("获得值") return self._temperature @temperature.setter def temperature(self, value): if value < -273: raise ValueError("零下273度是不可能的") print("设定值") self._temperature = value电气工程的计算机萌新:余登武。 写博文不容易,如果你觉得本文对你有用,请点个赞支持下,谢谢。
