下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922
论坛 >编程语言 >Python技巧和陷阱 (2)

Python技巧和陷阱 (2)

希尔瓦娜斯发布于 2018-03-15 09:17查看:851回复:1

控制流

当学习Python中的控制结构时,通常要认真学习 forwhile,if-elif-else, 和 try-except只要正确使用,这几个控制结构能够处理绝大多数的情况。也是基于这个原因,几乎你所遇到的所有语言都提供类似的控制结构语句。在基本的控制结构以外,Python也额外提供一些不常用的控制结构,这些结构会使你的代码更具可读性和可维护性。

Great Exceptations

Exceptions作为一种控制结构,在处理数据库、sockets、文件或者任何可能失败的资源时非常常用。使用标准的 try 、except 结构写数据库操作时通常是类型这样的方式。

image.png

你能发现这里的问题吗?这里有两种可能的异常会触发相同的except模块。这意味着查找数据失败(或者为查询数据建立连接失败)会引发回退操作。这绝对不是我们想要的,因为在这个时间点上事务并没有开始。同样回退也不应该是数据库连接失败的正确响应,因此让我们将不同的情况分开处理。

首先,我们将处理查询数据。

image.png

现在数据检索拥有自己的try-except,这样当我们没有取得数据时,我们可以采取任何处理方式。没有数据我们的代码不大可能再做有用的事,因此我们将仅仅退出函数。除了退出你也可以构造一个默认对象,重新进行检索或者结束整个程序。

现在让我们将commit的代码也单独包起来,这样它也能更优雅的进行错误处理。

image.png

实际上,我们已经增加了两端代码。首先,让我们看看else,当没有异常发生时会执行这里的代码。在我们的例子中,这里只是将事务成功的信息写入日志,但是你可以按照需要进行更多有趣的操作。一种可能的应用是启动后台任务或者通知。

很明显finally 子句在这里的作用是保证db.close() 总是能够运行。回顾一下,我们可以看到所有和数据存储相关的代码最终都在相同的缩进级别中形成了漂亮的逻辑分组。以后需要进行代码维护时,将很直观的看出这几行代码都是用于完成 commit操作的。

Context and Control

之前,我们已经看到使用异常来进行处理控制流。通常,基本步骤如下:

尝试获取资源(文件、网络连接等)

如果失败,清除留下的所有东西

成功获得资源则进行相应操作

写日志

程序结束

考虑到这一点,让我们再看一下上一章数据库的例子。我们使用try-except-finally来保证任何我们开始的事务要么提交要么回退。

image.png

我们前面的例子几乎精确的映射到刚刚提到的步骤。这个逻辑变化的多吗?并不多。

差不多每次存储数据,我们都将做相同的步骤。我们可以将这些逻辑写入一个方法中,或者我们可以使用上下文管理器(context manager)

image.png

上下文管理器通过设置代码段运行时需要的资源(上下文环境)来保护代码段。在我们的例子中,我们需要处理一个数据库事务,那么过程将是这样的:

连接数据库

在代码段的开头开始操作

在代码段的结尾提交或者回滚

在代码段的结尾清除资源

让我们建立一个上下文管理器,使用上下文管理器为我们隐藏数据库的设置工作。contextmanager 的接口非常简单。上下文管理器的对象需要具有一个__enter__()方法用来设置所需的上下文环境,还需要一个__exit__(exc_type, exc_val, exc_tb) 方法在离开代码段之后调用。如果没有异常,那么三个 exc_* 参数将都是None

此处的__enter__方法非常简单,我们先从这个函数开始。

image.png

__enter__方法只是返回数据库连接,在代码段内我们使用这个数据库连接来存取数据。数据库连接实际上是在__init__ 方法中建立的,因此如果数据库建立连接失败,那么代码段将不会执行。

现在让我们定义事务将如何在 __exit__ 方法中完成。这里面要做的工作就比较多了,因为这里要处理代码段中所有的异常并且还要完成事务的关闭工作。

image.png

现在我们就可以使用 DatabaseTransaction 类作为我们例子中的上下文管理器了。在类内部, __enter__ 和 __exit__ 方法将开始和设置数据连接并且处理善后工作。

image.png

为了改进我们的(简单)事务管理器,我们可以添加各种异常处理。即使是现在的样子,这个事务管理器已经为我们隐藏了许多复杂的处理,这样你不用每次从数据库拉取数据时都要担心与数据库相关的细节。

生成器

Python 2中引入的生成器(generators)是一种实现迭代的简单方式,这种方式不会一次产生所有的值。Python中典型的函数行为是开始执行,然后进行一些操作,最后返回结果(或者不返回)。

生成器的行为却不是这样的。

image.png

使用 yield 关键字代替 return ,这就是生成器的独特之处。当我们调用 my_generator('thing') 时,我得到的不是函数的结果而是一个生成器对象,这个生成器对象可以在任何我们使用列表或其他可迭代对象的地方使用。

更常见的用法是像下面例子那样将生成器作为循环的一部分。循环会一直进行,直到生成器停止 yield值。

image.png

生成器实例化之后不做任何事直到被要求产生数值,这时它将一直执行到遇到第一个 yield 并且将这个值返回给调用者,然后生成器保存上下文环境后挂起一直到调用者需要下一个值。

现在我们来写一个比刚才返回三个硬编码的值更有用的生成器。经典的生成器例子是一个无穷的斐波纳契数列生成器,我们来试一试。数列从1开始,依次返回前两个数之和。

image.png

函数中的 while True 循环通常情况下应该避免使用,因为这会导致函数无法返回,但是对于生成器却无所谓,只要保证循环中有 yield 。我们在使用这种生成器的时候要注意添加结束条件,因该生成器可以持续不断的返回数值。

现在,使用我们的生成器来计算第一个大于10000的斐波纳契数列值。

image.png

这非常简单,我们可以把数值定的任意大,代码最终都会产生斐波纳契数列中第一个大于X的值。

让我们看一个更实际的例子。翻页接口是应对应用限制和避免向移动设备发送大于50兆JSON数据包的一种常见方法。首先,我们定义需要的API,然后我们为它写一个生成器在我们的代码中隐藏翻页逻辑。

我们使用的API来自Scream,这是一个用户讨论他们吃过的或想吃的餐厅的地方。他们的搜索API非常简单,基本是下面这样。

image.png

他们将下一页的链接嵌入到API应答中,这样当需要获得下一页时就非常简单了。我们能够不考虑页码,只是获取第一页。为了获得数据,我们将使用常见的  库,并且用生成器将其封装以展示我们的搜索结果。

这个生成器将处理分页并且限制重试逻辑,它将按照下述逻辑工作:

  1. 收到要搜索的内容

  2. 查询scream-about-food接口

  3. 如果接口失败进行重试

  4. 一次yield一个结果

  5. 如果有的话,获取下一页

  6. 当没有更多结果时,退出

非常简单。我来实现这个生成器,为了简化代码我们暂时不考虑重试逻辑。

image.png

当我们创建了生成器,你只需要传入搜索的内容,然后生成器将会生成请求,如果结果存在则获取结果。当然这里有些未处理的边界问题。异常没有处理,当API失败或者返回了无法识别的JSON,生成器将抛出异常。

尽管存在这些未处理完善的地方,我们仍然能使用这些代码获得我们的餐厅在关键字“coffee”搜索结果中的排序。

image.png

如果使用Python 3,当你使用标准库时你也能使用生成器。调用类似 dict.items() 这样的函数时,不返回列表而是返回生成器。在Python 2中为了获得这种行为,Python 2中添加了 dict.iteritems() 函数,但是用的比较少。

收藏(0)0
查看评分情况

全部评分

此主贴暂时没有点赞评分

总计:0

回复分享

版主推荐

    共有1条评论

    • IT宅男
    • mr jack
    • Mr ken
    • Mright
    • cappuccino
    • YUI
    • 课课家运营团队
    • 课课家技术团队1
    • 酸酸~甜甜
    • 选择版块:

    • 标题:

    • 内容

    • 验证码:

    • 标题:

    • 内容

    • 选择版块:

    移动帖子x

    移动到: