Python-2-和-3-兼容性指南-二-

83 阅读44分钟

Python 2 和 3 兼容性指南(二)

原文:Python 2 and 3 Compatibility

协议:CC BY-NC-SA 4.0

五、包的导入

Python 3 对包内的导入进行了语法修改,要求我们使用相对导入语法。每个 Python 版本都有不同的包导入库;比如 URL 库 urllib.request 是针对 Python 3 的,urllib2 是针对 Python 2 的。本章介绍如何确保与相对导入的兼容性,以及如何基于 Python 版本导入合适的包。

在我详细讨论包导入的兼容性之前,让我带您回顾一下 Python 的导入基础设施。

Python 导入

像任何其他语言一样,当你开始使用 Python 时,你总是想知道如何导入其他模块或包以实现逻辑或代码重用。Python 有非常灵活的导入基础设施。我们可以通过以下方式执行包导入:

  • 常规导入

  • 使用来自

  • 本地导入

  • 可选导入

  • 相对导入

常规导入

常规导入是最常用的。它要求您使用 import 关键字,后跟要导入的模块或包。

**Download PackageImports/regular.py** 

**import** sys
**import** os, sys, time
**import** sys as system
**import** urllib.error

您可以在同一行中导入一个模块或多个模块或包。也可以使用 as 关键字根据自己的选择来重命名模块。子模块输入是使用点符号完成的。

注意

在同一行中导入多个模块违反了 Python 风格指南。推荐的方法是在新的一行上导入每个包或模块。

使用来自

当我们想要导入一个模块或包的一部分或特定部分,而不是整个模块或包时,我们使用这种语法。

**Download PackageImports/usingfrom.py** 

**from** os **import** path
**from** os **import** *
**from** os **import** path, walk
**from** os **import** (path, walk)
**from** os **import** path,  \
                walk

从包中导入一个特定的模块是非常简洁的,它向代码的读者提供了模块是从哪里导入的信息。

我们还可以决定使用*来导入所有内容。然而,这可能会使您的名称空间变得混乱。假设您定义了一个函数或顶级变量,它与一个导入的模块同名。如果您尝试使用操作系统模块中的那个,它将使用您定义的那个。因此,对于 Tkinter 模块这样的标准库模块,应该谨慎使用它。

您也可以使用中的在同一行导入多个项目。如果有很多项,建议您用括号将它们括起来。如果项目延续到下一行,使用 Python 的行延续字符,这是一个反斜杠。

本地导入

在脚本的顶部执行导入会将导入放在全局范围内,文件中的所有方法都可以访问它们。我们可以决定将导入放在局部范围内,比如放在方法中。

**Download PackageImports/local.py** 

**import** sys

**def** squareRoot(a):
    **import** math
    **return** math.sqrt(a)

sys 导入在全局范围内,而 math 导入在本地 squareRoot 方法范围内。当您在同一个脚本中定义另一个方法并试图使用 math 模块时,会导致导入错误;但是,脚本中的任何方法都可以使用 sys 模块。当要导入的模块被很少调用的函数使用时,在局部范围内导入模块是有益的。在这种情况下,您可以在方法的局部范围内导入模块。

可选导入

当您有一个想要使用的首选包或模块,但是您想要指定一个备用包以防第一个模块或包不存在时,可以使用可选导入。

**Download PackageImports/optional.py** 

try:
    from http.client import responses
except ImportError:
    try:
        from httplib import responses
    except ImportError:
        from BaseHTTPServer import BaseHTTPRequestHandler

总的想法是尝试导入所需的模块。如果模块不存在,那么在捕获异常时导入第二个模块。可选导入用于支持软件的多个版本或用于加速。我将在本章的后面讨论如何使用可选参数来实现中性兼容性。

相对导入

在 Python 2 中,当一个包内的模块需要相互引用时,可以使用 import foo 或 from foo import Bar。Python 3 和 Python 2 的现代版本具有相对路径导入的语法,如 PEP 302 中所述,通过使用句点来确定如何相对导入其他包或模块。假设我们有一个类似这样的文件结构:

RoboCop/
|
+--__init__.py
|
+--constants.py
|
+--robot.py
|
+--cop.py

现在假设 robot.py 需要从 cop.py 导入整个 constants.py 文件和一个类。

Python 2

让我们使用 from import 语法。

**Download PackageImports/relative2.py** 

import constants
from cop import SomeCop
蟒蛇 3

当您需要从包中的其他地方导入整个模块时,请使用新的 from。导入语法。句点实际上是从该文件(robot.py)到要导入的文件(constants.py)的相对路径。

**Download PackageImports/relative2.py** 

from . import constants
from . cop import SomeCop

在这种情况下,它们在同一个目录中,因此是单个句点。您也可以从父目录导入(从..导入另一个模块)或子目录。

我们已经研究了执行包或者模块导入的不同方法。Python 3 为相对导入引入了新的语法,一些库开发人员开发了不同的包,一些标准库基于 Python 版本有不同的名称。

在这些情况下,我们可以通过为重命名的模块使用可选导入,或者通过更改执行相对导入的语法来提供兼容性。让我详细解释一下这些选项。

重命名模块的兼容性

在 Python 的不同版本中,有些模块已经被重命名。其中一个模块是 HTTP 模块。我们使用 http.client 来访问 python 2 中的响应功能,但是我们使用 httplib 来访问 python 3 中的响应功能。为了在相同的代码库中实现兼容性,可以使用可选的导入。

**Download PackageImports/optional.py** 

try:
    from http.client import responses
except ImportError:
        from httplib import responses

如果第一个导入的模块不存在,让我们使用 try/except 块来捕获任何错误。然后对 except 块中的可选导入执行导入。对于像 HTTP 模块这样的重命名模块,我们导入 Python 2 http.client 包,它在 Python 2 中执行时没有错误。然而,当这个片段在 Python 3 上运行时,会抛出一个异常,在这种情况下,我们导入 Python 3 支持的 httplib 模块。

可选导入提供了导入可选依赖项的方法。它尝试在 try 块中加载依赖项,如果不存在,则在 except 块中加载依赖项。这样,我们可以在 Python 2 和 3 中导入重命名的模块。

相对导入的兼容性

假设我们有以下与包相关的导入:

Package/
|
+--__init__.py
|
+--module1.py
|
+--rmodule2.py
|
+--module3.py

在模块 1 中导入模块 2 有两种方法。我们可以使用 Python 3 语法进行相对导入,或者从内置的 future 模块导入 absolute_import。

相对路径导入语法

相对导入使用模块的 name 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息,那么相对导入将被解析,就像该模块是顶级模块一样,而不管该模块实际上位于文件系统的什么位置。

**from . import** submodule2

这将导入与当前包相关的子模块 2 模块。点号表示在包层次结构中提升多个级别。在这种情况下,我们在当前包的层次结构中向上移动一个级别。

使用 __ 未来 _ _

Python 3 关闭了隐式相对导入。为了在 Python 2 中实现这一点,我们可以通过使用 future 内置包中的 absolute_import 模块来关闭隐式相对导入。

**from __future__ import**  absolute_import

摘要

在这一章中,我们看了如何在 Python 2 和 Python 3 中实现重命名或具有不同名称的模块的兼容性。我们还研究了如何为相对导入提供兼容性。

任务:壁虎开发

您了解了很多关于包导入的兼容性。现在,你的导师 Ali 说他有一个来自 gecko-dev 项目的脚本,但最近没有时间。他需要你的帮助。该脚本执行 ByteIO。在他给你这个脚本的链接之前,他想让你了解一下下面的脚本。

**Download PackageImports/task.py** 

from StringIO import StringIO

*def test_BytesIO():* 
    fp = BytesIO()
    fp.write(b("hello"))
    assert fp.getvalue() == b("hello")

Ali 说,“这个脚本使用了 Python 2 StringIO 模块中的 BytesIO。无需深入细节,我想让你做的就是分别为 Python 2 和 3 的正确模块进行可选导入。”然后他说你任务的目标是导入 StringIO。用于 Python 2 和导入 IO 的 StringIO 模块。Python 3 的 StringIO。这是在 test_six.py 源文件脚本中,在 geck-dev 项目的 testing/we b-platform/tests/tools/six 目录中。

如果你已经提交,那么你现在可以检查他合并了什么。

**Download PackageImports/solution.py** 

try:
    from StringIO import StringIO
except ImportError:
        from io import BytesIO

*def test_BytesIO():* 
    fp = BytesIO()
    fp.write(b("hello"))
    assert fp.getvalue() == b("hello")

六、异常

Python 提供了一种非常简洁的方法来处理异常,但是 Python 2 和 3 都有自己独特的异常处理语法。在 Python 3 中,捕捉异常对象需要 as 关键字;用参数引发异常需要括号;并且字符串不能作为异常使用。本章描述如何实现引发、捕获异常和异常链的中立兼容性。

引发异常

Python 允许我们使用 raise 语句以多种方式引发异常。其他语言可能会使用 throw。使用 raise 语句时,我们可以指定三个参数,即异常类型、异常参数和回溯。使用不带任何参数的 raise 语句会重新引发上一个异常。引发异常的一般语法是:

raise [Exception [, args [, traceback]]]

异常参数和回溯是可选的。让我们看看如何以一种与 Python 2 和 3 都兼容的方式引发异常。我们将首先查看没有追溯的异常,然后查看有追溯的异常。

引发不带追溯的异常

引发不带回溯的异常通常涉及到只使用带有异常名(如 NameError)和异常参数的 raise 语句。典型的 Python 2 语法如下所示:

def func(value):
    raise ValueError, "funny value"

为了在两个 Python 版本中可靠地执行该语句,应该将其更改为以下代码片段:

def func(value):
   raise ValueError ("funny value")

我们在异常参数周围引入括号。

通过回溯引发异常

Python 还允许我们通过回溯来引发异常。回溯是从异常处理程序开始的堆栈跟踪,沿着调用链一直到引发异常的地方。以下是使用回溯引发异常的 Python 2 语法:

def func(value):
    traceback = sys.exc_info()[2]
    raise ValueError, "funny value", traceback

同样的方法也可以用 Python 3 编写,只是有一点小小的不同:异常参数用括号括起来,调用 with_traceback()方法。

def func(value):
    traceback = sys.exc_info()[2]
    raise ValueError ("funny value").with_traceback(traceback)

为了中立的兼容性,Python-future 和 six 都提供了引发异常的包装器。

使用 Python-未来

我们将从 future.utils 包中导入并使用 raise_ 来引发在 Python 2 和 3 上运行的异常。

from future.utils import raise_

def func(value):
    traceback = sys.exc_info()[2]
    raise_ (ValueError, "funny value", traceback)

当我们使用 raise_ 时,我们给它异常类型、参数和回溯作为参数。

此外,future 提供了使用 future.utils 包中的 raise_with_traceback 方法来实现相同功能的选项。

from future.utils import  raise_with_traceback

def func(value):
    raise_with_traceback(ValueError("dodgy value"))
使用六

类似地,我们可以从 six 包中导入并使用 raise_ 来引发运行在 Python 2 和 3 上的异常。

from six import raise_

def func(value):
    traceback = sys.exc_info()[2]
    raise_ (ValueError, "funny value", traceback)

当我们使用 raise_ 时,我们给它异常类型、参数和回溯作为参数。

总之,当提出兼容性异常时

  • 没有回溯的异常会在异常参数周围引入括号

  • 带回溯的异常使用 raise_ from Python-future 和 six

捕捉异常

我们需要一个 catch-all except 子句来捕捉 Python 中的异常。try 和 except 是用于捕捉异常的 Python 关键字。try 子句中的代码是逐语句执行的。如果发生异常,将跳过 try 块的其余部分,并执行 except 子句。

这是在 Python 2 中捕捉异常的一种简单方法:

(x,y) = (5,0)

try:
        z = x/y
except ZeroDivisionError, e:
        print e

except 子句可以将多个异常命名为带括号的元组;例如,(ZerDivionError,IdontLikeYouException)。在分隔异常和 e 变量的逗号之前。

在 Python 2 中,用逗号将异常与变量分开是可行的,但在 Python 3 中,这种做法已被弃用,并且不可行。为了中性兼容性,请使用 as 关键字而不是逗号。这是 Python 3 中的默认语法,但它在两个 Python 版本中都适用。

(x,y) = (5,0)
try:
    z = x/y
except ZeroDivisionError as e:
    z = e
    print z

as 关键字将异常分配给一个变量,比如我们的例子中的 e ,这也是最常见的,但是您可以给这个变量一个不同的名称。该变量是一个异常实例。

总之,使用 as 关键字(而不是逗号)是为了在捕获异常时保持兼容性。

异常链接

异常链在将捕获的异常包装在新异常中后重新引发该异常。原始异常被保存为新异常的属性。如果一个异常引起另一个异常,异常链是隐式的。这意味着第一个异常的信息在堆栈跟踪中是可用的,因为它存储在最后一个异常类的 context 属性中。如果我们在异常出现时关联一个新的异常,异常链也可能是显式的。这用于将一种异常类型转换成另一种。原始异常类型存储在 cause 属性中。

异常链接只在 Python 3 中可用,在 Python 3 中,我们可以编写以下代码:

Download PackageImports/exceptionchaining_Python3.py
try:
    v = {}['a']
except KeyError as e:
    raise ValueError('failed') from e

在 Python 2 中没有实现异常链接的直接方法,但是我们可以通过向异常类添加自定义属性来实现同样的目的,如下所示:

Download PackageImports/exceptionchaining_Python2.py

class MyError(Exception):
    def __init__(self, message, cause):
        super(MyError, self).__init__(message + u', caused by ' + repr(cause))
        self.cause = cause

try:
    v = {}['a']
except KeyError as e:
    raise MyError('failed', e)

我们将自定义属性添加到 MyError 类中。对于中性代码库,Python-future 和 six 都提供了一个 raise_from 包装器来处理异常链接。

使用 Python-未来

我们将从 future.utils 包中导入并使用 raise_from 模块。

Download PackageImports/exceptionchaining_future.py

from future.utils import raise_from

try:
    v = {}['a']
except KeyError as e:
    raise_from (MyError('failed', e))

raise_from 包装器替换了 raise 关键字。

使用六

我们将从 six 包中导入并使用 raise_from 模块。

Download PackageImports/exceptionchaining_six.py

from six import raise_from

try:
    v = {}['a']
except KeyError as e:
    raise_from (MyError('failed', e))

raise_from 包装器替换了 raise 关键字。

总之,使用 Python-future 的和 six 的 raise_from 包装器在异常链中实现兼容性。

摘要

我们研究了如何在 Python 2 代码库中支持 Python 3 来引发和捕捉异常,以及异常链接。

任务:阿里随机方法

今天,Ali 给你发了一封关于 Python 2 脚本的电子邮件,他希望你努力使它与 Python 3 兼容。

**Download PackageImports/task.py** 

def foo(i):
     l = [1,2,3]
     try:
         assert i >= 1
         return l[i]
     except TypeError,e:                                 
        print "dealing with TypeError"
     except IndexError, e:                               
         print "dealing with IndexError"
     except:                                                        
         print "oh dear"
     finally:                                                          
         print "the end"

他声称这个脚本的唯一问题是实现异常的方式。他们仍然使用 Python 2 语法,这在 Python 3 中不起作用。

以下内容已被合并。

**Download PackageImports/solution.py** 

def foo(i):
     l = [1,2,3]
     try:
         assert i >= 1
         return l[i]
     except TypeError as e:                                 
        print "dealing with TypeError"
     except IndexError as e:                               
         print "dealing with IndexError"
     except:                                                        
         print "oh dear"
     finally:                                                          
         print "the end"

语法已更改为使用 as,而不是不推荐使用的 Python 2 逗号。

七、HTML 处理

Python 总是附带一个 cgi 模块来转义不同的字符,但是这有局限性。从 Python 3.2 开始,HTML 模块克服了这些缺点。在 Python 2 和 3 中,HTML 解析和实体是使用不同的模块实现的,这使得兼容性更加难以实现。本章描述了在 Python 2 和 3 中实现 HTML 转义和解析的方法。

HTML 转义

在 3.2 之前的版本中,Python 总是附带一个带有 escape()函数的 cgi 模块来转义不同的字符。例如,下面的脚本将< to &lt, >转义为&gt 和& to &amp。

**Download HTMLProcessing/cgi_escapepy** 

import cgi
s = cgi.escape( """& < >""" )

这个脚本使用 cgi.escape()方法对&、>和

 cgi.escape(string_to_escape, quote=True)

cgi.escape 上可选的第二个参数对引号进行转义。默认情况下,它们不会被转义。cgi.escape()方法有一个限制:它不能转义除&、>和

html 模块

html 模块是在 Python 3.2 中引入的,用于对 HTML 标记中的保留字符进行转义。和 cgi 模块一样,它也有一个 escape()方法。

**Download HTMLProcessing/html_escape.py** 

import html

html.escape( """& < ‘ “ >""" )

html.escape()与 cgi.escape()的不同之处在于其缺省值为 quote=True。

因此,在两个 Python 版本中,有两种不同的方法来实现 HTML 转义,但我们需要找到一种统一的方法。python-未来可以拯救我们。

使用 Python-未来

Python-future 从其 html 模块中提供了 escape()方法包装器,以帮助我们避开版本差异:

**Download HTMLProcessing/html_future.py** 

from html import escape

html.escape( """& < ' " >""" )

HTML 解析

根据维基百科,解析或句法分析是根据正式语法的规则分析自然语言或计算机语言中的一串符号的过程。术语解析来自拉丁语 pars。

HTML 解析获取 HTML 代码并从中提取所有相关信息,包括段落、日期、粗体文本等等。

在 Python 2 中,HTML 解析实现为:

**Download HTMLProcessing/html_parsingf_py2.py** 
from HTMLParser import                               HTMLParser
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("Encountered a start tag:", tag)

    def handle_endtag(self, tag):
        print("Encountered an end tag :", tag)

    def handle_data(self, data):
        print("Encountered some data  :", data)

然而在 Python 3 中,模块从 HTMLParser 变成了 html.parser。

**Download HTMLProcessing/html_parsingf_py2.py** 
from HTMLParser import HTMLParser 
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("Encountered a start tag:", tag)

    def handle_endtag(self, tag):
        print("Encountered an end tag :", tag)

    def handle_data(self, data):
        print("Encountered some data  :", data)

因此,在两个 Python 版本中,有两种不同的方法来实现 HTML 解析,但是我们需要找到一种统一的方法来完成同样的工作。我们有三种选择:

  • 使用 Python-future 中的 html.parser

  • 使用 Python-future 中的 future.moves.html.parser

  • 使用来自 six.moves 的 html_parser

使用 Python-未来

Python-future 提供了两种选择:html.parser 模块和 future.moves.html.parser。

从 html.parser 使用 HTMLParser

让我们从 html.parser 模块导入相关的 HTMLParser。它允许我们达到预期的兼容性。

**Download HTMLProcessing/html_escape_future1.py** 
from html.parser import HTMLParser

class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("Encountered a start tag:", tag)

    def handle_endtag(self, tag):
        print("Encountered an end tag :", tag)

    def handle_data(self, data):
        print("Encountered some data  :", data)

使用 future.moves.html.parser 中的 HTMLParser

与第一个选项一样,我们将从 future.moves.html.parser 模块导入 HTMLParser。这将给我们预期的兼容性。

**Download HTMLProcessing/html_escape_future2.py** 

from future.moves.html.parser  import HTMLParser

class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("Encountered a start tag:", tag)

    def handle_endtag(self, tag):
        print("Encountered an end tag :", tag)

    def handle_data(self, data):
        print("Encountered some data  :", data)

使用六

six 提供了一个一致的接口来加载用于在 Python 2 或 Python 3 上解析 HTML 的模块。

**Download HTMLProcessing/html_escape_six.py** 

from six.moves import HTMLParser

class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("Encountered a start tag:", tag)

    def handle_endtag(self, tag):
        print("Encountered an end tag :", tag)

    def handle_data(self, data):
        print("Encountered some data  :", data)

摘要

我们研究了在转义和解析 HTML 时如何实现兼容性。接下来,我们将讨论文件的兼容性。

任务:Html 解析

下面是一个执行 HTML 解析的 Python 2 脚本。如果你需要澄清,你的导师会随时帮助你。

**Download HTMLProcessing/task.py** 
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print "Encountered a start tag:", tag
    def handle_endtag(self, tag):
        print "Encountered an end tag :", tag
    def handle_data(self, data):
        print "Encountered some data  :", data

使用六

这应该是

**Download HTMLProcessing/task_six.py** 

From six.moves import HTMLParser
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print "Encountered a start tag:", tag
    def handle_endtag(self, tag):
        print "Encountered an end tag :", tag
    def handle_data(self, data):
        print "Encountered some data  :", data

八、使用文件

每个操作系统都使用文件作为主要的存储机制,因此,有一些机制允许我们打开和读取这些文件。Python 也提供了打开、读取和写入文件的方法,但是 Python 2 和 3 在处理文件的方式上有很大的不同。本章解释了不同之处,并展示了使用 io.open 方法在两个版本上执行文件处理以实现兼容性的中性方法。

文件处理

在我们研究文件的兼容性之前,我将简单介绍一下一般的文件处理。在我们对文件做任何事情之前,我们必须打开文件。在版本 2 和版本 3 中,Python 都有一个内置的 open()函数,它以文件名作为参数。在 Python 3 中,它接受第二个参数,即编码参数。在 Python 2 中没有编码参数。

Python 2 中的文件处理采用以下形式:

**Download Files/open.py** 

f = open('some_file.txt')
data = f.read()              
text = data.decode('utf-8')

这个脚本打开并读取一个文件。数据变量包含读取的内容,即字节字符串。我们知道磁盘上的文件包含一系列字节。在大多数情况下,我们对字节序列不感兴趣,而是想要 Unicode 字符序列(字符串)。

为了将字节序列转换为 Unicode 字符序列,Python 必须根据特定的编码算法对字节进行解码。

在 Python 3 中,如果我们不指定字符编码,将使用默认编码,这可能不支持文件中的字符。在 Python 3 中,在不指定编码的情况下打开文件会这样做:

>>> file = open("email.docx")
>>> strw = file.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/Python3.5/codecs.py", line 321, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb7 in position 10: invalid start byte
>>>

首先,默认编码是依赖于平台的。如果其他人的默认字符编码是 UTF-8,前面的代码可能在他们的计算机上工作,但是如果他们的默认字符编码不是 UTF-8,它将在另一个人的计算机上失败。

补救方法是使用允许我们指定字符编码的方法,而不是使用默认编码,这是非常不一致的,并且不会给我们的程序带来和谐,因为默认字符编码是依赖于平台的。

有两种方法可以做到这一点:一种是使用 io.open(),另一种是使用 codecs.open()。这两种方法都采用第二个参数,即字符编码,但是 Python 3 使 codecs.open()过时了,这给我们留下了一个使用 io.open()的选项,因为它在 Python 2 和 3 中都有效。

如果您感兴趣,我将展示如何在 Python 2 中使用 codecs.open()指定字符编码。

**Download Files/codecs.py** 

import codecs
f = codecs.open('some_file.txt', encoding="utf-8")
注意

codecs.open()在 Python 3 中已被否决和废弃;使用 io.open()或内置的 open()。

io.open()

Python 2 和 3 都支持这种方法,它还允许我们指定字符编码。

要支持 Python 2.6 和更高版本,包括 Python 3.4,请使用 io.open(),它采用编码参数,而不是现在已过时的 codecs.open()。

要编写兼容的文件处理代码,我们有两种方法可以实现:

  • 打开文件时指定编码

  • 打开文件,读取字节,并用特定的编码对它们进行解码

打开文件时指定编码

我们可以将模式指定为 open 函数的第二个参数,在这种情况下,读取的数据不必使用给定的编码进行解码。

**Download Files/ioOpening.py** 

from io import open

f = open('myfile.txt', encoding='utf-8')
text = f.read()

我们使用 io 模块中的 open()方法,而不是内置的 open()方法。该方法将首先获取文件名,这是一个相对路径,但也会继续获取编码参数。根据文件中的数据,可以使用任何编码参数。在这个例子中,我简单地选择了 UTF-8。

不需要再次解码该数据;在打开时指定解码参数后,读取的是 Unicode 文本。

我们可以决定打开文件,并指定我们将使用 rt 关键字而不是使用 encoding 参数来读取 Unicode 文本。它以同样的方式工作。

**Download Files/ioDecodert.py** 

from io import open

f = open('some_file.txt', 'rt')
data = f.read()             

在这种情况下,读取的是 Unicode 文本;不需要解码步骤。您也可以打开并读取字节:

**Download Files/ioDecodert.py** 

from io import open

f = open('some_file.txt', 'rb)

指定解码时的编码

当解码读取的数据时,我们可以将编码参数指定为 decode 函数上的一个参数。

**Download Files/ioDecoderb.py** 

from io import open

f = open('some_file.txt', 'rb')
data = f.read()             
text = data.decode('utf-8')  

我们使用 io 模块中的 open()方法,而不是内置的 open()方法。第一个参数是文件名,它是一个相对路径。第二个参数指定我们应该使用 rb 读取字节。这是我们读取二进制文件的方法:通过使用带有 rb 或 wb 模式的 open()函数来读取或写入二进制数据。因此,当我们读取文件的内容时,它们是字节,应该使用特定的编码算法进行解码。

Python 3 open()内置

您一定想知道 Python 3 中内置的 open()发生了什么变化。它成为 io.open()的别名,其工作方式与 io.open()相同。这意味着它还需要第二个编码参数。因此,在 Python 3 中使用 open()内建函数采用以下形式:

**Download Files/opnepy3.py** 
f = open('myfile.txt', encoding='utf-8')
text = f.read()

它的用法与 io.open()的用法非常相似。

摘要

我们已经了解了如何以与 Python 2 和 3 兼容的方式处理文件。

任务:帮助堆栈溢出用户

今天有人在 Stack Overflow 上提问,在寻求帮助时提供了以下 Python 2 函数。你的导师说你可以帮助这个人。研究这段代码并重新实现它以使其兼容。

**Download Files/task.py** 

def read_text():
    quotes = open("C:\Python27\houston.txt")
    Contents = quotes.read()
    quotes.close()

“你对这个用户的最终回答应该与这个解决方案相似,”Ali 说。

**Download Files/task.py** 

def read_text():
    quotes = open("C:\Python27\houston.txt", "r")
    Contents = quotes.read()
    quotes.close()

九、类的自定义行为

在 Python 中,以 __ 开头和结尾的类方法名被称为特殊方法,因为它们允许我们定制 Python 使用我们的类的方式。在这一章中,我们来看看其中的一些方法,并学习如何在 Python 2 和 3 中实现兼容性。我们首先看一下自定义迭代器方法(iternext),然后再讨论 str 和 __ 非零 __ 方法。

自定义迭代器

Python 和其他语言中的迭代器是可以迭代的对象。迭代器遍布 Python。仅举几个例子,它们已经被下意识地实现在 for 循环、理解和生成器中。iterable 对象支持在其内容上创建迭代器。Python 中的大多数内置和容器——比如列表、元组和字符串——都是可迭代的。

在 Python 中,一个被称为迭代器的对象必须实现 iternext 特殊方法,统称为迭代器协议。这意味着我们可以使用 iter()和 next()特殊方法构建自己的自定义迭代器。iter()方法返回迭代器对象本身,而 next()方法返回迭代器的下一个值。如果没有更多的项目要返回,那么它将引发 StopIteration 异常。迭代器对象只能使用一次,因为在引发 StopIteration 异常后,它将继续引发同一个异常。

大多数特殊方法都不应该被直接调用;相反,我们使用 for 循环或列表理解,然后 Python 会自动调用这些方法。如果需要调用它们,使用 Python 的内置:iter 和 next。

注意

根据定义,iterable 是定义了 iter 方法的对象,iterator 是定义了 iternext 的对象,其中 iter 返回 iterator 对象,而 next 返回迭代中的下一个元素。

创建迭代器类后,我们可以创建一个迭代器,并使用 next()函数遍历它,或者更方便的是,我们可以使用 for 循环。一个迭代器对象必须穷尽或者说有一个结尾,这不是一条经验法则。迭代器对象可以是无限的,但是在处理这种迭代器时要非常小心。

在 Python 2 中,创建迭代器类的规则仍然有效(迭代器协议),但与 Python 3 不同,它使用 next()而不是 next special 方法。

**Download CustomBehaviourOfClasses/Python2_CustomIterator.**                                      **py** 

class PgCounter(object):
    def __init__(self, min, max):
        self.current = min
        self.max = max

    def __iter__(self):
        return self

    def next (self):
        if self.current > self.max:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

该类实现了 iter()方法,该方法以迭代器对象的形式返回自身。它还实现了 next()方法,该方法返回下一个值,直到 current 小于 max。这是一个遵循迭代器协议的完整迭代器类。

我们现在可以在代码中使用这个迭代器。

**Download CustomBehaviourOfClasses/Python2_CustomIteratorUse.py** 

itr = PgCounter('hello')
**assert** itr.next() == 'H'
**assert** list(itr) == list('ELLO')

为了使前面的 Python 类及其方法逻辑中立兼容,我们有三种选择。

  • 来自未来内置模块的子类对象

  • 使用将来的@implements_iterator 装饰器

  • 子类迭代器并使用 advance_iterator()方法

未来内置模块的子类对象

我们之前看到,要创建定制迭代器,我们必须实现 iternext。我们还知道,在 Python 2 中,next 是 next()。Python 2 中使用的 object 子类允许 next(),但仍然提供 next -> next 别名。

**Download CustomBehaviourOfClasses/CustomIterator_builtins.py** 

from builtins import object

class PgCounter(object):
    def __init__(self, min, max):
        self.current = min
        self.max = max

    def __iter__(self):
        return self

    def __next__ (self):
        if self.current > self.max:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

    __metaclass__ =  MyMeta
    pass

当给定 next -> next 别名时,我们可以使用 Python 3 语法(next)并在迭代器接口的两个版本中具有兼容性。迭代器现在也可以以 Python 3 的方式在我们的代码中使用。

**Download CustomBehaviourOfClasses/CustomIteratorUse_builtins.py** 

itr = PgCounter('hello')
**assert** next() == 'H'
**assert** list(itr) == list('ELLO')

我们现在使用 Python 3 语法 next(),而不是 itr.next()。

总之,

  1. 从未来的内置模块中导入对象类。

  2. 未来内置模块中的对象类为我们提供了 next -> next alias 功能。

  3. 对迭代器接口使用 Python 3 语法(next),并使用 Python 3 风格(next())获取迭代器中的下一个值。

未来的@implements_iterator 装饰器

future 允许迭代器接口(next)的 Python 3 语法获得迭代器中的下一个值。

**Download CustomBehaviourOfClasses/CustomIterator_future.py** 

from future.utils import implements_iterator

@implements_iterator
class PgCounter(object):
    def __init__(self, min, max):
        self.current = min
        self.max = max

    def __iter__(self):
        return self

    def __next__ (self):
        if self.current > self.max:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

从 future.utils 模块导入装饰器,并像前面一样装饰迭代器类。获取 iterable 中的下一个值。

**Download CustomBehaviourOfClasses/CustomIteratorUse_future.py** 

itr = PgCounter('hello')
**assert** next() == 'H'
**assert** list(itr) == list('ELLO')

我们仍然使用 Python 3 语法 next(),而不是 itr.next()。

总之,

  1. 从 future.utils 导入@implements_iterator。

  2. 修饰迭代器类。

  3. 并对迭代器接口使用 Python 3 语法,获取迭代器中的下一项。

迭代器类和 Advanced_iterator()来自 six

Six 提供了一个迭代器类,帮助我们创建可移植的迭代器。这个类应该是子类,子类应该实现一个 next 方法。

**Download CustomBehaviourOfClasses/CustomIterator_six.py** 

import six

class PgCounter(six.Iterator):
    def __init__(self, min, max):
        self.current = min
        self.max = max

    def __iter__(self):
        return self

    def __next__ (self):
        if self.current > self.max:
            raise StopIteration
        else:
            self.current += 1
            return self.current – 1

这个迭代器类在 Python 2 中只有一个方法。在 Python 3 中是空的;它只是对象的别名。

为了获得迭代器中的下一项,我们使用 advanced_iterator()方法来实现我们的兼容性目标。

**Download CustomBehaviourOfClasses/CustomIteratorUse_six.py** 

itr = PgCounter('hello')
**assert** six.advance_iterator (itr) == 'H'
**assert** list(itr) == list('ELLO')

six.advanced_iterator()方法返回迭代器中的下一项。这取代了我们在 Python 2 中调用 itr.next()和在 Python 3 中调用 next(itr)的需要。

总之,

  1. 导入六。

  2. 用 six.Iterator 子类化迭代器类。

  3. 使用 six.advanced_iterator()方法,而不是 Python 3 中的 next()和 Python 2 中的 itr.next()。

  4. six.advanced_iterator()方法将迭代器实例作为参数。

注意

建议所有代码都使用内置的 next()。

练习 7.1

使以下脚本兼容。

**Download CustomBehaviourOfClasses/CustomIterator_Exercise.py** 
def test_iterator():
    class myiter(object):
        def next(self):
            return 13
    assert myiter().next()== 13
    class myitersub(myiter):
        def next(self):
            return 14
    assert myitersub().next() == 14

自定义 str 方法

str 是一种特殊的方法,类似于 initnext、和 iter。它用于返回对象的字符串表示形式。

这里有一个例子:

**Download CustomBehaviourOfClasses/str_example.py** 

Class MyDate:
    def __str__(self):
        return "The date today"

这是 MyDate 对象的 str 方法。当我们打印这个类的对象时,Python 调用 str 方法。

>>> date = MyDate()
>>> print  date
The date today

str 方法有一个密切相关的方法 repr,它也返回对象的字符串表示形式,但有细微的区别。以下面的代码为例。

>>> num = 4
>>> repr (num)
'4'
>>> str(4)
'4'
>>> name = "jason"
>>> repr (name)
"'jason'"
>>> str (name)
'jason'
>>> name2 = eval(repr(name))
>>> name3 = eval(str(name))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'jason' is not defined

对于整数,repr()和 str()的结果是相同的,但是对于字符串,我们开始看到不同之处。最大的区别是当我们试图使用方法的结果作为 eval() *的参数时。*str 对象的 repr()实现可以用作 eval() 的参数,返回有效的 str 对象。str()实现没有返回有效值。

根据官方 Python 文档,repr 被称为官方对象表示,而 str 是正式表示。有人认为,一个正式的代表应该可以被 eval()调用,并且必须返回相同的对象。

str 在某些情况下也更好,因为对象的 str 表示比 repr 表示更具可读性。

>>> from datetime import datetime
>>> now = datetime.now()
>>> repr(now)
'datetime.datetime(2017, 8, 10, 17, 23, 34, 64394)'
>>> str(now)
'2017-08-10 17:23:34.064394'
>>>

repr()给出的输出对开发中的调试非常有用,而 str 的输出可能对应用的用户有用。

注意

strrepr 方法分别用于 builtin str()和 repr()方法中。

练习 7.2

打印下列类的字符串表示。

**Download CustomBehaviourOfClasses/str_exercise.py** 
     class cartesian:
          def __init__(self, x=0, y=0):
              self.x, self.y = x, y
          def distanceToOrigin(self):
              return floor(sqrt(self.x**2 + self.y**2))
      class manhattan:
          def __init__(self, x=0, y=0):
              self.x, self.y = x, y
          def distanceToOrigin(self):
              return self.x + self.y

在 Python 2 中,str 方法返回字节。如果我们想要返回字符,那么我们必须实现另一个返回字符的 unicode()方法。

**Download CustomBehaviourOfClasses/str_py2.py** 

class Name():
    def __unicode__(self):
        return 'character string: \u5b54\u5b50'
    def __str__(self):
        return unicode(self).encode('utf-8')

a = MyClass()
print(a)

我们应该将所有字符串格式放在 unicode()方法中,然后创建 str()方法作为调用 unicode()的存根。

在 Python 3 中,str()返回字符串。有一个相关的 bytes 特殊方法返回字节。

**Download CustomBehaviourOfClasses/str_py3.py** 

class Name():
    def __str__(self):
        return u "Je nun"

a = MyClass()
print(a)

print 语句返回一个字符串。

练习 7.3

打印以下代码的字节字符串表示。

**Download CustomBehaviourOfClasses/byte_exercise.py** 

class Name():
    def __str__(self):
        return u "Je nun"

a = MyClass()
print(a)

Python 2 代码实现了 str 方法,如图所示。为了支持 Python 3,我们可以使用未来库提供的@ Python _ 2 _ unicode _ compatible decorator 或@six。Python _ 2 _ unicode _ 兼容六个装饰器。让我们来详细了解一下这些装饰者。

未来:@ Python _ 2 _ unicode _ compatible Decorator

@ Python _ 2 _ unicode _ compatible decorator 接受实现 str 方法的方法类。

**Download CustomBehaviourOfClasses/str_future.py** 

from future.utils import Python_2_unicode_compatible

@Python_2_unicode_compatible
class SomeClass(object):
    def __str__(self):
        return u' some unicode : \u5b54\u5b50'

res = SomeClass()
print(res)

在 Python 3 中,decorator 什么都不做,但是在 Python 2 中,它会用 unicode 来别名 str

总之,

  1. 从 future.utils 导入 Python_2_unicode_compatible。

  2. 用@Python_2_unicode_compatible 修饰这个类。

  3. 使用 Python 3 语法。

练习 7.4

使用 future 使以下 Python 2 脚本与 Python 2 和 Python 3 兼容。hello 字符串应该打印为字符串,然后打印为字节字符串。提示:解决方案应该实现 bytestr

**Download CustomBehaviourOfClasses/str_future_exercise.py** 

def test_Python_2_unicode_compatible():
    class MyTest(object):
        def __unicode__(self):
                return 'hello'
               def __str__(self):
           return unicode(self).encode('utf-8')

            def __str__(self):
           return ‘hello’

    my_test = MyTest()

六:@Python_2_unicode_compatible 装饰器

与 future 类似,six 有一个@ Python _ 2 _ unicode _ compatible decorator,它采用实现 str 方法的方法类。

**Download CustomBehaviourOfClasses/str_six.py** 

import six

six.@Python_2_unicode_compatible
class SomeClass(object):
    def __str__(self):
        return u' some unicode : \u5b54\u5b50'

res = SomeClass()
print(res)

在 Python 3 中,装饰器不做任何事情,但会将 str 别名为 unicode 并创建一个 str 方法,该方法返回 unicode()的结果,这是一个用 UTF-8 编码的字符串。

总之,

  1. 导入六。

  2. 用@Python_2_unicode_compatible 修饰这个类。

  3. 使用 Python 3 语法。

练习 7.5

使用 six 使下面的 Python 2 脚本兼容 Python 2 和 Python 3。

**Download CustomBehaviourOfClasses/str_six_exercise.py** 
    class TestClass(object):
        def __unicode__(self):
                       return 'character string: \u5b54\u5b50'
                def __str__(self):
            return unicode(self).encode('utf-8')
        def __str__(self):
            return 'hello'

    class TestClass2(object):
        pass

自定义布尔方法

当在类上调用 bool()时,自定义布尔方法定义类实例行为。

在 Python 2 中,我们实现 __ 非零 _ _()方法来定义这种行为。

**Download CustomBehaviourOfClasses/booleanpy2.py** 
class test(object):
def __nonzero__(self):
     return False

在 Python 3 中,bool()替换了 __ 非零 __()。

**Download CustomBehaviourOfClasses/booleanp3.py** 
class test(object):
def __bool__(self):
     return False

为了兼容,我们可以把 bool 等同于 __ 非零 __。

**Download CustomBehaviourOfClasses/soluion1.py** 
class test(object):
def __bool__(self):
     return False
__nonzero__ = __bool__

使用 Python-未来

我们也可以导入 future 的内置模块并保留 Python 3 的语法。

**Download CustomBehaviourOfClasses/future.py** 

以下内容来自 builtins 导入对象。

class test(object):
def __bool__(self):
     return false

摘要

我们看了 Python 的神奇方法:str 、__ 非零 __ 、inter、next。这些方法在 Python 2 和 3 中有不同的语法。

我们还讨论了在 six 和未来版本中提供的兼容性选项。

任务:简单的方法

对于今天的练习,你的导师说有一个小方法仍然使用 Python 2 的 __ 非零 _ _ 但是你的工作是使它兼容。这是剧本。

**Download CustomBehaviourOfClasses/task.py** 
class Foo(object):
    def __init__(self):
        self.bar = 3
    def __bool__(self):
        return self.bar > 10

他把正确且兼容的脚本藏在了某个地方。将您的结果与以下未来选项进行比较。

**Download CustomBehaviourOfClasses/task_future.py** 
from buitlins import object
class Foo(object):
    def __init__(self):
        self.bar = 3
    def __bool__(self):
        return self.bar > 10

十、集合和迭代器

众所周知,Python 3 避免返回列表和字典。如果 k 是一个字典,在 Python 2 中,k.keys()返回字典中的键的列表,而在 Python 3 中,k.keys()支持在类似 for 循环的方式中对键进行迭代。类似地,在 Python 2 中,k.values()和 k.items()返回值列表或键值对,而在 Python 3 中,我们只能在 for 循环中迭代值。Python 3 只包含 range,其行为类似于 Python 2 的 xrange,而在 Python 3 中,内置地图的行为类似于 Python 2 的 itertools.imap。因此,在 Python 2 和 3 中,为了获得“旧地图”行为,我们将使用 list(map(...)).本章讨论字典、范围和映射函数的兼容性。

可迭代字典成员

我需要提到的是,Python 2 和 3 中的 dict.keys()、dict.values()和 dict.items()操作的工作方式是不同的。在将 Python 2 代码改编为 Python 3 时,当我们试图以列表形式对 key()、values()和 items()进行操作时,会得到一个 TypeError。

在 Python 3 dict.keys()不像 Python 2 那样返回列表。相反,它返回一个视图*对象。*返回的 dict_keys 对象更像是一个集合,而不是一个列表。它实际上返回给定字典的可迭代键。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(mydict .keys())
<class 'dict_keys'>
>>> mydict.keys()
dict_keys(['echo', 'again'])

Python 2 中有一个 dict.iterkeys()方法来返回可迭代的字典键,该方法的工作方式与 dict.keys()相同。为了在 Python 2 和 3 中获得可迭代的键,我们使用 six.iterkeys。

**Download CollectionsandIterators/six_keys.py** 

import six

mydict = { 'echo': "lima", 'again': "golf" }
for key in six.iterkeys(mydict):
    do_stuff(key)

six.iterkeys()方法取代了 Python 2 中的 dictionary.iterkeys()和 Python 3 中的 dictionary.keys()。

还有另一种中立的方法,通过引用字典本身来获得可迭代的键。例如,这段 Python 2 代码:

**Download CollectionsandIterators/keyspy2.py** 

mydict = { 'echo': "lima", 'again': "golf" }
for key in mydict.iterkeys():
    do_stuff(key)

和这个是一样的:

**Download CollectionsandIterators/keyspy2and3.py** 

mydict = { 'echo': "lima", 'again': "golf" }
for key in mydict:
    do_stuff(key)

这在 Python 2 和 3 中都有效。

总之,对于可迭代字典键的中性兼容性:

  • 使用 six.iterkeys(),它取代了 Python 3 中的 dictionary.keys()和 Python 2 中的 dictionary.iterkeys()。

  • 您也可以参考字典来获得可迭代的键。

价值观念

在 Python 2 中,如果我们想要使用字典值的 iterable,那么我们编写如下所示的代码:

**Download CollectionsandIterators/valuespy2.py** 

mydict = { 'echo': "lima", 'again': "golf" }
for value in heights.itervalues():
    do_stuff(value)

我们在字典中调用 itervalues()。相比之下,在 Python 3 中,我们必须调用字典中的 values()来达到同样的目的。

**Download CollectionsandIterators/valuespy3.py** 

mydict = { 'echo': "lima", 'again': "golf" }
for value in heights.values():
    do_stuff(value)

为了同时支持 Python 2 和 3,我们必须使用其中一个选项找到一个中间点。使用:

  • itervalues()从六

  • python-future 内置模块中的 itervalues()

Python-future 内置模块中的 itervalues()

itervalues()方法将字典作为参数。

**Download CollectionsandIterators/valuesbuiltins.py** 

from builtins import itervalues
mydict = { 'echo': "lima", 'again': "golf" }
for value in itervalues(mydict):
    do_stuff(value)
itervalues()从六

与 future 内置的 itervalues()一样,six 的 itervalues()方法也将字典作为参数,并返回字典的可迭代值。

**Download CollectionsandIterators/values_six.py** 
from six import itervalues 
mydict = { 'echo': "lima", 'again': "golf" }
for value in itervalues(mydict):
    do_stuff(value)

itervalues()方法取代了 Python 2 中的 dictionary.itervalues()和 Python 3 中的 dictionary.values()。

总之,对于可迭代字典值的中性兼容性:

  • 调用 six.itervalues(),将字典作为参数。这取代了 Python 3 中的 dictionary.values 和 Python 2 中的 dictionary.itervalues()。

  • 您还可以从 future 的 builtins 模块调用 itervalues(),并将字典作为参数。

项目

类似地,dictionary.items()返回 Python 2 和 dict_item 视图对象中的字典项列表。为了在 Python 2 中获得可迭代项,我们使用 dict.iteritems()。

**Download CollectionsandIterators/itemspy2.py** 

mydict = { 'echo': "lima", 'again': "golf" }
for (key, value) in heights.iteritems():
    do_stuff(key, value)

而在 Python 3 中,我们使用 dict.items()。

**Download CollectionsandIterators/itemspy3.py** 

mydict = { 'echo': "lima", 'again': "golf" }
for (key, value) in heights.items():
    do_stuff(key, value)

有几种方法可以实现兼容性。未来六号有包装器。

iteritems()未来包装

我们可以使用未来的 iteritems()方法来获取可迭代的项。从未来导入 iteritems 模块会覆盖 Python 2 的 iteritems()方法功能和 Python 3 的 items()功能。

**Download CollectionsandIterators/items_future.py** 

from future.utils import iteritems

mydict = { 'echo': "lima", 'again': "golf" }
for (key, value) in initeritems(mydict):
    do_stuff(key, value)
iteritems()六包装器

类似地,six.iteritems()替换了 Python 3 中的 dictionary.items()和 Python 2 中的 dictionary.iteritems()。

**Download CollectionsandIterators/items_future.py** 

from future.utils import iteritems

mydict = { 'echo': "lima", 'again': "golf" }
for (key, value) in iteritems(mydict):
    do_stuff(key, value)

列表形式的字典成员

在 Python 2 中,我们简单地使用 keys()从字典中返回一个键列表。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(mydict .keys())
<type 'list'>
>>> mydict .keys()
['echo', 'again']
>>> mydict .keys()[0]
'echo'
注意

我说过 dict.keys() dict.values() *、*和 dict.items()返回的对象称为视图对象。视图只是字典上的窗口,显示字典的成员,即使在字典发生变化之后。成员(键、值)列表包含给定时间的字典成员的副本。列表可以做一些事情,但是视图是非常动态的,创建起来更加容易和快速,因为我们不需要创建任何成员(键,值)的副本来创建它们。

但是正如前面强调的,在 Python 3 中,dict.keys()返回一个 iterable。在 Python 3 中,我们实际上可以通过简单地将返回值转换为列表来重现返回列表的想法。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(list(mydict.keys()))
<class 'list'>
>>> list(mydict .keys())
['echo', 'again']
>>> list(mydict .keys())[0]
'echo'

这在 Python 2 和 3 中都能很好地工作。

价值观念

像键一样,dict.values()返回字典 dict 中的值列表。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(mydict .values())
<type 'list'>
>>> mydict .values()
['lima', 'golf']
>>> mydict .values()[0]
'lima'

在 Python 3 中,这将返回一个 dict_values 对象。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(mydict .values())
<type 'dict_values'>

兼容性可以通过将 dict_values 对象转换成一个列表来维护,该列表可以像在 Python 2 和 3 中一样被索引。

**Download CollectionsandIterators/values_list.py** 

mydict = { 'echo': "lima", 'again': "golf" }
valuelist = list(mydict.values())

这样做是可行的,但是在 Python 2 中有一个缺点。效率非常低。让我们讨论一下在未来的六年中我们可以使用的类似技巧。

使用 Python-未来

Future 有一个来自 future.utils 模块的 listvalues()方法,我们应该使用字典作为参数来调用它。

**Download CollectionsandIterators/values_list_future1.py** 

from future.utils import listvalues

mydict = { 'echo': "lima", 'again': "golf" }
valuelist = listvalues(mydict)

结果是字典中的值列表。

我们还可以使用 future.utils 模块中的 itervalues()方法来获取字典值的 iterable。

**Download CollectionsandIterators/values_list_future2.py** 

from future.utils import itervalues

mydict = { 'echo': "lima", 'again': "golf" }
values = itervalues(mydict)
valuelist = list(values)

因为这个方法返回一个 iterable,我们应该把它的结果转换成一个列表。

使用六

像 future 一样,six 有一个 itervalues()方法,该方法将字典作为参数,并返回字典值的 iterable。

**Download CollectionsandIterators/values_list_six.py** 

from six import itervalues

mydict = { 'echo': "lima", 'again': "golf" }
values = itervalues(mydict)
valuelist = list(values)

因为这个方法返回一个 iterable,我们应该把它的结果转换成一个列表。

项目

返回字典 dict 中的条目列表。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(mydict .items())
<type 'list'>

在 Python 3 中,这将返回一个 dict_item 对象。

>>> mydict = { 'echo': "lima", 'again': "golf" }
>>> type(mydict .items()
<type 'dict_items'>

为了兼容,将 dict_items 对象转换成一个列表,然后可以在 Python 2 和 3 中正常索引。

**Download CollectionsandIterators/items_list.py** 

mydict = { 'echo': "lima", 'again': "golf" }
itemlist = list(mydict.items())

这样做是可行的,但是在 Python 2 中有一个缺点。效率也很低。让我们来讨论一下类似的技巧。

使用 Python-未来

Future 有一个来自 future.utils 模块的 listitems()方法,我们应该使用字典作为参数来调用它。

**Download CollectionsandIterators/items_list_future1.py** 

from future.utils import iteritems

mydict = { 'echo': "lima", 'again': "golf" }
itemlist = iteritems(mydict)

结果是字典中的条目列表。

我们还可以使用 future.utils 模块中的 iteritems()方法来获取字典项的 iterable。

**Download CollectionsandIterators/items_list_future2.py** 

from future.utils import iteritems

mydict = { 'echo': "lima", 'again': "golf" }
items = iteritems(mydict)
itemlist = list(items)

因为这个方法返回一个 iterable,我们应该把它的结果转换成一个列表。

使用六

像 future 一样,six 有一个 iteritems()方法,该方法将字典作为参数,并返回字典项的 iterable。

**Download CollectionsandIterators/items_list_future2.py** 

from six import iteritems

mydict = { 'echo': "lima", 'again': "golf" }
items = itemitems(mydict)
itemlist = list(items)

地图

一般来说,map()函数将给定的函数应用于 iterable 的每一项——它可以是列表、元组等等*。*根据 Python 版本的不同,它返回不同的结果。

在 Python 2 中,它返回的结果是一个列表。

**Download CollectionsandIterators/map_py2.pp** 

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares)         #returns list type

在 Python 3 中,结果不是列表,而是可迭代的。

**Download CollectionsandIterators/map_py3.pp** 

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares)         #returns an iterable

返回值的结果不属于列表类型。

我们利用 map 并在 Python 2 和 3 中返回相同结果的一种方法是将结果转换为列表。

**Download CollectionsandIterators/map_alt1.pp** 

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = list (map(square, numbers))
type(squares)         #returns a list type

这将返回一个列表类型的结果,但是在 Python 2 中这是非常低效的。

我们还可以使用可选导入将 itertools.imap 作为 map 导入到 try except 块中,但是 except 块中没有任何内容。Python 2 中不存在 itertools.map 模块。

**Download CollectionsandIterators/map_alt2.pp** 

*try:* 
    import itertools.imap as map
except ImportError:
    pass

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares)         #returns a list type

这将 itertools.imap 作为地图导入,这在 Python 3 中运行良好,因为 itertools.imap 的工作方式与 Python 2 中的地图相同。在 Python 2 中,导入失败,因为我们没有该模块,因此执行 except 块,我们知道它什么也不做,从而执行默认的 map()函数行为。

让我们讨论使用 six 和 Python-future 实现地图功能兼容性的不同方法。

使用 Python-未来

Python-future 在其内置模块中提供了一个 map 方法,该方法返回一个类似于 Python 3 中的 iterable。

**Download CollectionsandIterators/map_future1.py** 

from builtins import map

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = list (map(square, numbers))
type(squares)         #returns a list type

我们必须把结果列成一个清单。

作为另一种选择,future 提供了 future.utils 模块中的 lmap()方法。该方法返回列表。

**Download CollectionsandIterators/map_future2.py** 

from future.utils import lmap

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = lmap(square, numbers)
type(squares)         #returns a list type

Future 还有一个来自 past.builtins 模块的 map()方法。使用时,它返回一个预期的列表结果。

**Download CollectionsandIterators/map_future3.py** 

from past.builtins import map

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = lmap(square, numbers)
type(squares)         #returns a list type
使用六

six 带有 six.moves 的 map()方法,我们可以调用它来代替标准的 map()函数。

**Download CollectionsandIterators/map_six.py** 

from six.moves import map

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = list(map(square, numbers))
type(squares)         #returns a list type

这相当于 Python 3 中的 itertools.imap()和 Python 2 中的 map,它们都返回可迭代的结果。因此,我们必须将结果转换为列表。

交互邮件访问协议

imap()函数将给定的函数应用于 iterable 的每一项,并返回 Python 2 中的 iterable。

**Dwnload CollectionsandIterators/imap_py2.py** 
from itertools import imap

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = imap(square, numbers)
type(squares) #this is an iterable

在 Python 3 中,这是不赞成的,通过内置的 map 方法可以实现相同的行为。

**Dwnload CollectionsandIterators/imap_py2.py** 
def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares) #this is an iterable

为了保持兼容性,我们可以选择将 Python 2 的 itertools.imap 作为 map 导入。这在 Python 2 中返回一个 iterable。

**Download CollectionsandIterators/imap_alt1.py** 
try:
    import itertools.imap as map
except ImportError:
    pass

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares) #this is an iterable

在 Python 3 中,try 块失败,从而执行 except 块,从技术上讲,except 块什么也不做。这导致执行 builtin map()函数,该函数返回一个 iterable。这是方法之一;让我们讨论一下六号和未来的选择。

使用六

six 带有 six.moves 的 map()方法,我们可以调用它来代替前面指出的标准 map()函数。

**Download CollectionsandIterators/imap_six.py** 

from six.moves import map

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares)         #returns a list type

这相当于 Python 3 中的 itertools.imap()和 Python 2 中的 map,它们都返回可迭代的结果。

使用 Python-未来

Python-future 在其内置模块中提供了一个 map 方法,该方法返回一个类似于 Python 3 中的 iterable。

**Download CollectionsandIterators/map_future1.py** 

from builtins import map

def square(x):
    return x*x

numbers = [1,2,3,4,5,6]
squares = map(square, numbers)
type(squares)         #returns a list type

结果是 Python 2 和 3 中的 iterable。

范围

Python 2 和 3 都有内置的范围函数,可以返回数字序列。端点不是生成序列的一部分。

Python 2 有两个生成数字序列的函数,它们包括 range()和 xrange()。内置的 range()函数以列表的形式返回一系列数字。

>>> range(1,10)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

函数的作用是:返回一个 xrange 对象。

>>> type(xrange(10))
<type 'xrange'>
>>>

在 Python 3 中,range()函数的工作方式与 xrange()在 Python 2 中的工作方式相同。

>>> type(xrange(10))
<type 'xrange'>
>>>

为了兼容,我们必须坚持使用 range()函数并将结果转换成列表。

**Download CollectionsandIterators/range_sol1.py** 
range_list = list(range(5))
assert range_list== [0, 1, 2, 3, 4]

Future 和 six 为我们提供了几个兼容性选项。

使用 Python-未来

第一个选项是使用内置模块中的 range()函数。这个函数做 Python 3 的范围所做的事情。

**Download CollectionsandIterators/range_future1.py** 

**from builtins import range** 
range_list = list(range(5))
assert range_list== [0, 1, 2, 3, 4]

为了生成序列的列表,我们将结果转换为列表。

或者,我们可以使用 future 的 lrange()函数。

**Download CollectionsandIterators/range_future2.py** 

**from future.utils import lrange** 
range_list = lrange(5)
assert range_list == [0, 1, 2, 3, 4]

还有一个来自 past.builtins 的 range()函数,作为 Python 2 的 range 函数。

**Download CollectionsandIterators/range_future2.py** 

**from past.builtins import range** 
range_list = range(5)
assert range_list == [0, 1, 2, 3, 4]

使用六

使用 six.moves.range 来保持兼容性。这个函数产生了一个可迭代的对象。

**Download CollectionsandIterators/range_six.py** 

**from six.moves import range** 
range_iter = range(5)
assert list(range_iter) == [0, 1, 2, 3, 4]

如果我们想生成一个列表形式的序列,那么我们把结果转换成一个列表。

摘要

本章介绍了字典和版本之间的不同变化。我们看到在 Python 3 中 items()、keys()和 values()返回 iterables,而在 Python2 中返回 lists。为了兼容,我们将这些操作的结果改为一个列表。在 Python 2 中,这降低了效率;我们讨论了使用 6 和未来更有效的方法。

我们还讨论了 map 和 imap 的区别。Python 3 中不赞成使用 imap,所以 map 会做 imap 会做的事情。为了兼容,当需要 list 类型的结果时,我们将结果转换为 list。还介绍了更清洁、更高效的 six 应用技术及发展前景。

最后,我们看了 Python 2 的 range()和 xrange()函数。range()生成列表形式的结果,而 xrange()生成可迭代的结果。为了便于移植,使用 range()并将生成的结果更改为列表。six 和 future 还使用 builtins.range 和 six.moves.range 提供了更好的选项。

任务:循环器-主

Project cycler-master 有一个测试方法仍然使用 Python 2 语法。您的任务是使这个方法兼容 Python 2 和 3。

**Download CollectionsandIterators/task.py** 
def test_getitem():
    c1 = cycler(3, xrange(15))
    widths = range(15)
    for slc in (slice(None, None, None),
                slice(None, None, -1),
                slice(1, 5, None),
                slice(0, 5, 2)):
        yield _cycles_equal, c1[slc], cycler(3, widths[slc])

使用 Python-future,这将变成:

**Download CollectionsandIterators/task_future.py** 
from builtins import range
def test_getitem():
    c1 = cycler(3, range(15))
    widths = list(range(15))
    for slc in (slice(None, None, None),
                slice(None, None, -1),
                slice(1, 5, None),
                slice(0, 5, 2)):
        yield _cycles_equal, c1[slc], cycler(3, widths[slc])

有了六个,这就变成了:

**Download CollectionsandIterators/task_six.py** 
from six.moves import range
def test_getitem():
    c1 = cycler(3, range(15))
    widths = list(range(15))
    for slc in (slice(None, None, None),
                slice(None, None, -1),
                slice(1, 5, None),
                slice(0, 5, 2)):
        yield _cycles_equal, c1[slc], cycler(3, widths[slc])