Python 3.3 为改进代码中的异常处理所做的工作
Python

> 
> 探索异常处理和其他未被充分利用但仍然有用的 Python 特性。
> 
> 
> 


![](/data/attachment/album/202105/29/221357mxpj2kitltdez6zj.jpg "Coding on a computer")


这是 Python 3.x 首发特性系列文章的第四篇。Python 3.3 于 2012 年首次发布，尽管它已经发布了很长时间，但它引入的许多特性都没有得到充分利用，而且相当酷。下面是其中的三个。


### yield from


`yield` 关键字使 Python 更加强大。可以预见的是，人们都开始使用它来创建整个迭代器的生态系统。[itertools](https://docs.python.org/3/library/itertools.html) 模块和 [more-itertools](https://more-itertools.readthedocs.io/en/stable/) PyPI 包就是其中两个例子。


有时，一个新的生成器会想要使用一个现有的生成器。作为一个简单的（尽管有点故意设计）的例子，设想你想枚举所有的自然数对。


一种方法是按照“自然数对的和，自然数对的第一项”的顺序生成所有的自然数对。用 `yield from` 来实现这个方法是很自然的。


`yield from <x>` 关键字是以下的简称：



```
for item in x:
    yield item

```


```
import itertools

def pairs():
    for n in itertools.count():
        yield from ((i, n-i) for i in range(n+1))

```


```
list(itertools.islice(pairs(), 6))

```


```
    [(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (2, 0)]

```

### 隐式命名空间包


假设有一个叫 Parasol 的虚构公司，它制造了一堆东西。它的大部分内部软件都是用 Python 编写的。虽然 Parasol 已经开源了它的一些代码，但其中一些代码对于开源来说过于专有或专业。


该公司使用内部 [DevPI](https://opensource.com/article/18/7/setting-devpi) 服务器来管理内部软件包。对于 Parasol 的每个 Python 程序员来说，在 PyPI 上找一个未使用的名字是没有意义的，所以所有的内部包都被称为 `parasol.<business division>.<project>`。遵守最佳实践，开发人员希望包的名字能反映出这个命名系统。


这一点很重要！如果 `parasol.accounting.numeric_tricks` 包安装了一个名为 `numeric_tricks` 的顶层模块，这意味着依赖这个包的人将无法使用名为 `numeric_tricks` 的 PyPI 包，不管它写的有多好。


然而，这给开发者留下了一个两难的选择：哪个包拥有 `parasol/__init__.py` 文件？从 Python 3.3 开始，最好的解决办法是把 `parasol`，可能还有 `parasol.accounting`，变成没有 `__init__.py` 文件的 [命名空间包](https://www.python.org/dev/peps/pep-0420/)。


### 抑制异常的上下文


有时，在从异常中恢复的过程中出现的异常是一个问题，有上下文来跟踪它是很有用的。然而，有时却不是这样：异常已经被处理了，而新的情况是一个不同的错误状况。


例如，想象一下，在字典中查找一个键失败后，如果不能分析它，则希望失败并返回 `ValueError()`。



```
import time

def expensive_analysis(data):
    time.sleep(10)
    if data[0:1] == ">":
        return data[1:]
    return None

```

这个函数需要很长的时间，所以当你使用它时，想要对结果进行缓存：



```
cache = {}

def last_letter_analyzed(data):
    try:
        analyzed = cache[data]
    except KeyError:
        analyzed = expensive_analysis(data)
        if analyzed is None:
            raise ValueError("invalid data", data)
        cached[data] = analyzed
    return analyzed[-1]

```

不幸的是，当出现缓存没有命中时，回溯看起来很难看：



```
last_letter_analyzed("stuff")

```


```
    ---------------------------------------------------------------------------

    KeyError                                  Traceback (most recent call last)

    <ipython-input-16-a525ae35267b> in last_letter_analyzed(data)
          4     try:
    ----> 5         analyzed = cache[data]
          6     except KeyError:


    KeyError: 'stuff'

```

在处理上述异常的过程中，发生了另一个异常：



```
    ValueError                                Traceback (most recent call last)

    <ipython-input-17-40dab921f9a9> in <module>
    ----> 1 last_letter_analyzed("stuff")
   

    <ipython-input-16-a525ae35267b> in last_letter_analyzed(data)
          7         analyzed = expensive_analysis(data)
          8         if analyzed is None:
    ----> 9             raise ValueError("invalid data", data)
         10         cached[data] = analyzed
         11     return analyzed[-1]


    ValueError: ('invalid data', 'stuff')

```

如果你使用 `raise ... from None`，你可以得到更多可读的回溯：



```
def last_letter_analyzed(data):
    try:
        analyzed = cache[data]
    except KeyError:
        analyzed = expensive_analysis(data)
        if analyzed is None:
            raise ValueError("invalid data", data) from None
        cached[data] = analyzed
    return analyzed[-1]

```


```
last_letter_analyzed("stuff")

```


```
    ---------------------------------------------------------------------------

    ValueError                                Traceback (most recent call last)

    <ipython-input-21-40dab921f9a9> in <module>
    ----> 1 last_letter_analyzed("stuff")
   

    <ipython-input-20-5691e33edfbc> in last_letter_analyzed(data)
          5         analyzed = expensive_analysis(data)
          6         if analyzed is None:
    ----> 7             raise ValueError("invalid data", data) from None
          8         cached[data] = analyzed
          9     return analyzed[-1]


    ValueError: ('invalid data', 'stuff')

```

### 欢迎来到 2012 年


尽管 Python 3.3 在十年前就已经发布了，但它的许多功能仍然很酷，而且没有得到充分利用。如果你还没有，就把它们添加到你的工具箱中吧。




---


via: <https://opensource.com/article/21/5/python-33>


作者：[Moshe Zadka](https://opensource.com/users/moshez) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
This is the fourth in a series of articles about features that first appeared in a version of Python 3.x. Python 3.3 was first released in 2012, and even though it has been out for a long time, many of the features it introduced are underused and pretty cool. Here are three of them.

## yield from

The `yield`

keyword made Python much more powerful. Predictably, everyone started using it to create a whole ecosystem of iterators. The [itertools](https://docs.python.org/3/library/itertools.html) module and the [more-itertools](https://more-itertools.readthedocs.io/en/stable/) PyPI package are just two examples.

Sometimes, a new generator will want to use an existing generator. As a simple (if somewhat contrived) example, imagine you want to enumerate all pairs of natural numbers.

One way to do it is to generate all pairs in the order of `sum of pair, first item of pair`

. Implementing this with `yield from`

is natural.

The `yield from <x>`

keyword is short for:

```
for item in x:
yield item
```


```
import itertools
def pairs():
for n in itertools.count():
yield from ((i, n-i) for i in range(n+1))
```


`list(itertools.islice(pairs(), 6))`


` [(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (2, 0)]`


## Implicit namespace packages


Imagine a fictional company called Parasol that makes a bunch of stuff. Much of its internal software is written in Python. While Parasol has open sourced some of its code, some of it is too proprietary or specialized for open source.

The company uses an internal [DevPI](https://opensource.com/article/18/7/setting-devpi) server to manage the internal packages. It does not make sense for every Python programmer at Parasol to find an unused name on PyPI, so all the internal packages are called `parasol.<business division>.<project>`

. Observing best practices, the developers want the package names to reflect that naming system.

This is important! If the package `parasol.accounting.numeric_tricks`

installs a top-level module called `numeric_tricks`

, this means nobody who depends on this package will be able to use a PyPI package that is called `numeric_tricks`

, no matter how nifty it is.

However, this leaves the developers with a dilemma: Which package owns the `parasol/__init__.py`

file? The best solution, starting in Python 3.3, is to make `parasol`

, and probably `parasol.accounting`

, to be [namespace packages](https://www.python.org/dev/peps/pep-0420/), which don't have the `__init__.py`

file.

## Suppressing exception context

Sometimes, an exception in the middle of a recovery from an exception is a problem, and having the context to trace it is useful. However, sometimes it is not: the exception has been handled, and the new situation is a different error condition.

For example, imagine that after failing to look up a key in a dictionary, you want to fail with a `ValueError()`

if it cannot be analyzed:

```
import time
def expensive_analysis(data):
time.sleep(10)
if data[0:1] == ">":
return data[1:]
return None
```


This function takes a long time, so when you use it, you want to cache the results:

```
cache = {}
def last_letter_analyzed(data):
try:
analyzed = cache[data]
except KeyError:
analyzed = expensive_analysis(data)
if analyzed is None:
raise ValueError("invalid data", data)
cached[data] = analyzed
return analyzed[-1]
```


Unfortunately, when there is a cache miss, the traceback looks ugly:

`last_letter_analyzed("stuff")`


```
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-16-a525ae35267b> in last_letter_analyzed(data)
4 try:
----> 5 analyzed = cache[data]
6 except KeyError:
KeyError: 'stuff'
```


During handling of the above exception, another exception occurs:

```
ValueError Traceback (most recent call last)
<ipython-input-17-40dab921f9a9> in <module>
----> 1 last_letter_analyzed("stuff")
<ipython-input-16-a525ae35267b> in last_letter_analyzed(data)
7 analyzed = expensive_analysis(data)
8 if analyzed is None:
----> 9 raise ValueError("invalid data", data)
10 cached[data] = analyzed
11 return analyzed[-1]
ValueError: ('invalid data', 'stuff')
```


If you use `raise ... from None`

, you can get much more readable tracebacks:

```
def last_letter_analyzed(data):
try:
analyzed = cache[data]
except KeyError:
analyzed = expensive_analysis(data)
if analyzed is None:
raise ValueError("invalid data", data) from None
cached[data] = analyzed
return analyzed[-1]
```


`last_letter_analyzed("stuff")`


```
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-21-40dab921f9a9> in <module>
----> 1 last_letter_analyzed("stuff")
<ipython-input-20-5691e33edfbc> in last_letter_analyzed(data)
5 analyzed = expensive_analysis(data)
6 if analyzed is None:
----> 7 raise ValueError("invalid data", data) from None
8 cached[data] = analyzed
9 return analyzed[-1]
ValueError: ('invalid data', 'stuff')
```


## Welcome to 2012

Although Python 3.3 was released almost a decade ago, many of its features are still cool—and underused. Add them to your toolkit if you haven't already.

## Comments are closed.