一.迭代器
迭代器在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 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")
五.@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())
如上所示,每当我们分配或检索任何对象属性(如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
电气工程的计算机萌新:余登武。
写博文不容易,如果你觉得本文对你有用,请点个赞支持下,谢谢。