python中的迭代器,生成器,闭包,装饰器,@property

一.迭代器

迭代器在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__())
python中的迭代器,生成器,闭包,装饰器,@property_第1张图片

2for循环
在正常编码中,我们常常使用for循环来迭代

for element in iterable:
    # 对元素做点什么

其真实 for 循环 内部代码为

# 创建一个迭代器对象iterable
iter_obj = iter(iterable)

# 无限循环
while True:
    try:
        # 获取下一项
        element = next(iter_obj)
        # 对元素做点什么
    except StopIteration:
        # 如果引发StopIteration,则从循环中断
        break
python中的迭代器,生成器,闭包,装饰器,@property_第2张图片

因此,在内部,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__())
python中的迭代器,生成器,闭包,装饰器,@property_第3张图片

二.生成器

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))
python中的迭代器,生成器,闭包,装饰器,@property_第4张图片

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])

# 同样的事情可以使用生成器表达式来完成
# 输出:  at 0x0000000002EBDAF8>
a=(x**2 for x in my_list)
print('a:',a)
print('next(a):',next(a))
python中的迭代器,生成器,闭包,装饰器,@property_第5张图片

三.闭包

1嵌套函数中的非局部变量
在了解闭包是什么之前,我们必须首先了解什么是嵌套函数和非局部变量。
在另一个函数内部定义的函数称为嵌套函数。嵌套函数可以访问封闭范围的变量。
在Python中,默认情况下,这些非本地变量是只读的,并且我们必须将它们明确声明为非本地变量(使用nonlocal关键字)才能进行修改。
以下是访问非局部变量的嵌套函数的示例。

def print_msg(msg):
# 这是外部封闭函数

    def printer():
# 这是嵌套函数
        print(msg)

    printer()

# 我们执行这个函数
# 输出: Hello
print_msg("Hello")
python中的迭代器,生成器,闭包,装饰器,@property_第6张图片

2定义闭包函数

在上面的示例中,如果函数print_msg()的最后一行返回了printer()函数而不是调用它,将会发生什么? 这意味着功能定义如下。

def print_msg(msg):
# 这是外部封闭函数

    def printer():
# 这是嵌套函数
        print(msg)

    return printer  # 这变了

# 现在,让我们尝试调用此函数。
# 输出: Hello
another = print_msg("Hello")
another()
python中的迭代器,生成器,闭包,装饰器,@property_第7张图片

这很不寻常。
print_msg()函数用字符串调用,"Hello"返回的函数绑定到另一个名称。在调用时another(),尽管我们已经完成了print_msg()函数的执行,但仍然记得该消息。
这种将一些数据(“Hello”)附加到代码上的技术在Python中称为闭包。
即使变量超出范围或函数本身已从当前命名空间中删除,也会记住封闭范围中的这个值。

3闭包的条件
从上面的实例可以看出,在Python中,当嵌套的函数在其封闭的范围内引用一个值时,我们有一个闭包。
以下几点总结了在Python中创建闭包必须满足的条件。

  1. 我们必须有一个嵌套函数(函数在函数内部)。
  2. 嵌套函数必须引用在封闭函数中定义的值。
  3. 封闭函数必须返回嵌套函数。

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)
python中的迭代器,生成器,闭包,装饰器,@property_第8张图片

四.装饰器

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()
python中的迭代器,生成器,闭包,装饰器,@property_第9张图片

在上面显示的示例中,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()
python中的迭代器,生成器,闭包,装饰器,@property_第10张图片

相当于

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))
python中的迭代器,生成器,闭包,装饰器,@property_第11张图片

通过这种方式,我们可以装饰带有参数的函数。
敏锐的观察者会注意到,inner()装饰器内部的嵌套函数的参数与其装饰的函数的参数相同。考虑到这一点,现在我们可以使通用装饰器可以使用任意数量的参数。
在Python中,此魔术是通过完成的function(*args, **kwargs)。这样,args是位置参数的元组,kwargs而是关键字参数的字典。这样的装饰器的一个实例是。

def works_for_all(func):
    def inner(*args, **kwargs):
        print("我可以装饰任何函数")
        return func(*args, **kwargs)
    return inner

5.链接装饰器
可以在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")
python中的迭代器,生成器,闭包,装饰器,@property_第12张图片

五.@property

在定义和详细了解@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())
python中的迭代器,生成器,闭包,装饰器,@property_第13张图片

如上所示,每当我们分配或检索任何对象属性(如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()。 这就是属性的作用。
python中的迭代器,生成器,闭包,装饰器,@property_第14张图片

任何检索温度值的代码都将自动调用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

在这里插入图片描述
电气工程的计算机萌新:余登武。
写博文不容易,如果你觉得本文对你有用,请点个赞支持下,谢谢。

在这里插入图片描述

你可能感兴趣的:(学习杂记,python,生成器)