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

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

原文:Python 2 and 3 Compatibility

协议:CC BY-NC-SA 4.0

一、打印、反引号和repr

打印是 Python 2 和 Python 3 之间最广为人知的区别。Python 2 中的 print 语句不需要括号;它是 Python 3 中的一个函数。反勾号 repr 也有区别。本章讨论了 Python 版本之间这些特性的差异以及实现其兼容性的技术。

打印

正如本书介绍中所讨论的,Python 2 中的 print 语句变成了 Python 3 中的一个函数,这意味着我们必须将想要打印的内容放在括号中。在我们开始转换一些脚本之前,让我用 future 和六个模块解释几个“阳”到印刷中性兼容“阴”。

使用 __ 未来 _ _

future 是一个内置模块,具有兼容性模块。对于 Python 2 中任何带有 print 语句的模块,必须在文件的第一行代码中使用 future import,后跟 print_function 模块。

from __future__  print_function

然后用函数形式;每当使用 print 语句时,print("something ")。

以下 Python 2 代码

Download Print/py2_print.py

Import sys

print >> sys.stderr, 'echo Lima golf'
print 'say again'
print  'I say again', 'echo Lima golf'
print  'Roger',

成为

Download Print/future_print.py

from __future__ import print_function
import sys

print('echo lima golf', file=sys.stderr)
print  ('say again')
print  ('I say again', 'echo lima golf')
print ( 'Roger', end='')

在 Python 2 中,带有一个参数的 print 函数打印参数中的给定字符串。对于多个字符串,如果我们不导入 print_function 来覆盖 Python 2 的行为,一个 tuple 就会打印出来。如果我们想使用 Python 3 语法打印一个元组,那么我们需要使用更多的括号。

# this prints a tuple in Python 2 if print_function is not imported
print  ('I say again', 'echo Lima golf')

#prints a tuple in Python 3
print  (('I say again', 'echo Lima golf')).

要在 Python 3 中打印元组,请使用双括号,如前面的代码所示。现在让我们看看如何使用 six 来保持与打印功能的兼容性。

使用六

以下内容用于任何具有 print 语句的模块,以及任何使用 six import 访问包装器打印函数的模块,该函数有助于实现中立的 Python 兼容性:

import six

然后使用 six.print_ (args,,file=sys.stdout,end="\n ",sep= ",flush=False)函数作为 Python 2 和 Python 3 之间打印语法差异的包装器。

以下 Python 2 打印语句

Download Print/py2_print.py

import sys

print >> sys.stderr, 'echo lima golf'
print 'say again'
print  'I say again', 'echo lima golf'
print  'Roger'

成为

Download Print/six_print.py

import six
import sys

six.print_('echo lima golf', file=sys.stderr)
six.print_('say again')
six.print_('I say again', 'echo lima golf')
six.print_('Roger', file=sys.stdout, end='')
The function prints                                   the arguments separated by sep. end is written after the last argument is printed. If flush is true, file.flush()                                                                                 is called after all data is written.

使用 future 来避免引入许多依赖项。

注意

您可以在其他模块导入后导入六个及以后的模块。future 是特殊的,必须首先导入。

任务:介绍你的导师

让我向您介绍 Ali,一位红帽工程师和开源导师,您将与他一起完成这些有趣的任务。顺便说一句,他是个好人。QEMU 项目的维护人员告诉他,项目中的一些 Python 脚本只支持 Python 2。这是你的第一个任务。Ali 通知您,您将只处理这两种方法中的打印语句,因为它们在同一个模块中。看一看。

Download Print/qmp.py

def cmd_obj(self, qmp_cmd):
        """
        Send a QMP command to the QMP Monitor.

        @param qmp_cmd: QMP command to be sent as a Python dict
        @return QMP response as a Python dict or None if the connection has
                been closed
        """
        if self._debug:
            print >>sys.stderr, "QMP:>>> %s" % qmp_cmd
        try:
            self.__sock.sendall(json.dumps(qmp_cmd))
        except socket.error as err:
            if err[0] == errno.EPIPE:
                return
            raise socket.error(err)
        resp = self.__json_read()
        if self._debug:
            print >>sys.stderr, "QMP:<<< %s" % resp
        return resp

def _execute_cmd(self, cmdline):
        if cmdline.split()[0] == "cpu":
                # trap the cpu command, it requires special setting
                try:
                idx = int(cmdline.split()[1])
                if not 'return' in self.__cmd_passthrough('info version', idx):
                        print 'bad CPU index'
                        return True
                self.__cpu_index = idx
                except ValueError:
                print 'cpu command takes an integer argument'
                return True
        resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
        if resp is None:
                print 'Disconnected'
                return False
        assert 'return' in resp or 'error' in resp
        if 'return' in resp:
                # Success
                if len(resp['return']) > 0:
                print resp['return'],
        else:
                # Error
                print '%s: %s' % (resp['error']['class'], resp['error']['desc'])
        return True

“这很简单。让我们把打印语句改成使用 future 和六个库的函数,”Ali 说。经过一些修改和检查,方法中的打印语句终于成型了。让我们来看看合并了什么。

使用 __ 未来 _ _

所做的只是从 future 导入 print_function,并保持 Python 3 的语法使其工作。

Download Print/future_qmp.py

from __future__ import print_function
import sys
def cmd_obj(self, qmp_cmd):
        """
        Send a QMP command to the QMP Monitor.

        @param qmp_cmd: QMP command to be sent as a Python dict
        @return QMP response as a Python dict or None if the connection has
                been closed
        """
        if self._debug:
                print("QMP:>>> %s" % qmp_cmd, file=sys.stderr)
        try:
                self.__sock.sendall((json.dumps(qmp_cmd)).encode('utf-8'))
        except socket.error as err:
                if err[0] == errno.EPIPE:
                return
                raise
        resp = self.__json_read()
        if self._debug:
                print("QMP:<<< %s" % resp, file=sys.stderr)  
        return resp

def _execute_cmd(self, cmdline):
        if cmdline.split()[0] == "cpu":
                # trap the cpu command, it requires special setting
                try:
                idx = int(cmdline.split()[1])
                if not 'return' in self.__cmd_passthrough('info version', idx):
                        print ('bad CPU index')
                        return True
                self.__cpu_index = idx
                except ValueError:
                print ('cpu command takes an integer argument')
                return True
        resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
        if resp is None:
                print ('Disconnected')
                return False
        assert 'return' in resp or 'error' in resp
        if 'return' in resp:
                # Success
                if len(resp['return']) > 0:
                print (resp['return'],  file=sys.stdout, end='')
        else:
                # Error
                print ('%s: %s' % (resp['error']['class'], resp['error']['desc']))
return True 

使用六

在第六个例子中,只要有 print 语句,我们就使用 print_ wrapper 函数,这个函数“神奇地”起作用了。

Download Print/six_qmp.py

import six
import sys
def cmd_obj(self, qmp_cmd):
        """
        Send a QMP command to the QMP Monitor.

        @param qmp_cmd: QMP command to be sent as a Python dict
        @return QMP response as a Python dict or None if the connection has
                been closed
        """
        if self._debug:
                six.print_("QMP:>>> %s" % qmp_cmd, file=sys.stderr)
        try:
                self.__sock.sendall((json.dumps(qmp_cmd)).encode('utf-8'))
        except socket.error as err:
                if err[0] == errno.EPIPE:
                return
                raise
        resp = self.__json_read()
        if self._debug:
                six.print_("QMP:<<< %s" % resp, file=sys.stderr)  
        return resp

def _execute_cmd(self, cmdline):
        if cmdline.split()[0] == "cpu":
                # trap the cpu command, it requires special setting
                try:
                idx = int(cmdline.split()[1])
                if not 'return' in self.__cmd_passthrough('info version', idx):
                        six.print_ ('bad CPU index')
                        return True
                self.__cpu_index = idx
                except ValueError:
                six.print_ ('cpu command takes an integer argument')
                return True
        resp = self.__cmd_passthrough(cmdline, self.__cpu_index)
        if resp is None:
six.print_ ('Disconnected') 
                return False
        assert 'return' in resp or 'error' in resp
        if 'return' in resp:
                # Success
                if len(resp['return']) > 0:
                six.print_ (resp['return'],  file=sys.stdout, end='')
        else:
                # Error
                six.print_ ('%s: %s' % (resp['error']['class'], resp['error']['desc']))
        return True

你们在最后一个补丁上做得很好。你的下一个任务将在 backtick repr 上。但是在我揭示任务之前,让我解释一些关于 backtick repr 的事情。

反勾符

Python 中的一个反勾是将包含的表达式转换成字符串的操作符。反勾号是 repr()和 str()方法的别名,因为它们都给出相同的结果。下面是一个例子。

Download Print/backtick.py

class backtick_example(object):
        def __repr__(self):
        return 'repr backtick_example'
        def __str__(self):
        return 'str backtick_example'
>>> a = backtick_example()

>>> repr(a)
#'repr backtick_example'

>>> `a`
#'repr backtick_example'

>>> str(a)
#'repr backtick_example'

然而,反斜线已经被弃用,并且不在 Python 3 中。相反,只要需要反勾号,我们就使用 repr。因此,这段代码:

Download Print/qmp_print.py

def _print(self, qmp):
        indent = None
        if self._pretty:
                indent = 4
        jsobj = json.dumps(qmp, indent=indent)
        print `jsobj`

更改为:

Download Print/new_qmp_print.py

def _print(self, qmp):
        indent = None
        if self._pretty:
                indent = 4
        jsobj = json.dumps(qmp, indent=indent)
        print repr(jsobj)

在单一代码库中实现中性兼容性。

注意

我们可以在不使用第三方模块的情况下实现中立的兼容性,因为有些结构可能已经被弃用,比如这个例子,或者在 Python 2 和 3 中有相同行为的语法。尽可能减少代码中的依赖性。

共存陷阱

当尝试 print 语句的中性兼容性时,在进行兼容性更改时,很容易在语义上破坏代码。这方面的主要例子将在本书后面的章节中讨论。

在开始您的共存项目之前,您需要有覆盖工具来检查代码是否在两个 Python 版本中运行并给出相同的结果。

您希望单元测试到位,以确保任何不正确的更改都会通过失败的测试通知您。

摘要

我们讨论了 Python 2 和 Python 3 之间最常见的区别,即 print 语句和反勾号 repr。为了打印语句的兼容性,我们可以通过从内置的 future 模块导入 print_function 来保持 Python 语法,或者使用 six 的 print_ wrapper 函数。我还注意到反斜线已经被弃用,所以只要需要反斜线,我们就应该使用 repr。

任务:将它们放在一起

您的任务是处理 fuel_dev_tools/docker 目录下的 astute.py 源文件中的 fuel-dev-tools-master 项目。该任务要求 astute.py 脚本在 Python 2 和 Python 3 上一致运行。

**Download Print/astute.py** 

def build_gem(self, source_dir): 
cmd = ( 
'cd %(cwd)s && ' 
'gem build %(gemspec)s' 
) % { 
'cwd': source_dir, 
'gemspec': self.gemspec, 
} 

try: 
result = subprocess.check_output([ 
cmd 
], shell=True) 

self.print_debug(result) 
except subprocess.CalledProcessError as e: 
print 'GEM BUILD ERROR' 
error =`e.output` 
print error 
raise 

Ali 已经在 IRC 上联系你讨论新任务了。你赶紧告诉他,这一次,你知道该怎么做了。稍微黑了一下,看看合并了什么。交叉手指,看看您的更改是否会出现在合并的补丁中。

使用六

这个解决方案使用了来自 six 的 print_ function。因此,使用 six.print_()函数代替 print 语句。

**Download Print/six_astute.py** 

import six 
def build_gem(self, source_dir): 
cmd = ( 
'cd %(cwd)s && ' 
'gem build %(gemspec)s' 
) % { 
'cwd': source_dir, 
'gemspec': self.gemspec, 
} 

try: 
result = subprocess.check_output([ 
cmd 
], shell=True) 

self.print_debug(result) 
except subprocess.CalledProcessError as e: 
six.print_('GEM BUILD ERROR') 
error =repr(e.output) 
six.print_(error) 
Raise 

使用未来

future 模块解决方案只需要 print_function 的一次导入。它保留了 Python 3 的语法。

**Download Print/future_astute.py** 
from __future__ import print_function 
def build_gem(self, source_dir): 
cmd = ( 
'cd %(cwd)s && ' 
'gem build %(gemspec)s' 
) % { 
'cwd': source_dir, 
'gemspec': self.gemspec, 
} 
try: 
result = subprocess.check_output([ 
cmd 
], shell=True) 
self.print_debug(result) 
except subprocess.CalledProcessError as e: 
print ('GEM BUILD ERROR') 
error =repr(e.output) 
print (error) 
raise 

二、数字

当在单个代码库中实现 Python 2 和 Python 3 的兼容性时,除非使用双斜线,否则 division 不会截断,或者 _ _ future _ _ importation to division 已完成。所有的整数都是长整数;没有短整数,因为 long()没有了;八进制常量必须以 0o 开头(零-oh);在整数检查中有一些语法变化需要注意。让我们详细讨论其中的每一个。

检查整数

在 Python 3 中,没有长整数;因此,根据 int 类型检查整数类型;例如:

**Download Numbers/inspectionpy3.py** 
y = 3
if isinstance(y, int):
    print ("y is an Integer")
else:
    print ("y is not an integer")

为了与仍然检查长类型的现有 Python 2 代码兼容,Python-future 提供了几个选项。

使用 Python-future 的内置模块

让我们使用 int,因为它在 Python 2 中同时匹配 int 和 long。

**Download Numbers/builtins_inspection.py** 

from builtins import int

y = 3
if isinstance(y, int):
    print ("y is an Integer")
else:
    print ("y is not an integer")

使用 Python-future 中的 past.builtins

在进行整数检查时,利用 Python-future 的 past.builtins 模块。

**Download Numbers/past_inspection.py** 

from past.builtins import long

y = 3
if isinstance(y, (int, long)):
    print ("y is an Integer")
else:
    print ("y is not an integer")

我们从 past.builtins 导入 long,然后检查 int 和 long,因为我们要检查的数字在 Python 2 中匹配 int 和 long,而在 Python 3 中只匹配 int。

使用六

six 提供了一个 integer_types 常量,该常量在 Python 版本之间有所不同。Python 2 中的整数类型是 long 和 int,而在 Python 3 中,只有 int。

**Download Numbers/six_inspection.py** 

import six

y = 3
if isinstance(y, six.integer_types):
    print ("y is an Integer")
else:
    print ("y is not an integer")

长整数

在 Python 3 中,long 变成了 int,因此不是短整型。为了兼容,我们对长整型和短整型都使用 int 然而,Python-future 对此也有一条出路。

X = 9223389765478925808                # not x =  9223389765478925808L

使用 Python-future 的内置模块

这里,我们利用内置模块来表示在 Python 2 和 Python 3 中一致工作的整数。

**Download Numbers/future_long.py** 

from builtins import int

longint = int(1) 

楼层划分

整数除法,或称底数除法,是将除法的结果向下舍入。在 Python 2 中,我们使用一个正斜杠来实现整数除法,而不是在 Python 3 中使用两个正斜杠。为了中立的兼容性,我们通过在划分时使用双正斜杠来使用 Python 3 划分。

**Download Numbers/integer_division.py** 

x, y = 5, 2

result = x // y

assert result == 2     # Not 2

浮点除法

在真除法或浮点除法中,结果不会向下舍入。在 Python 2 中,我们确保其中一个值是浮点数来实现这一点,而不是在 Python 3 中使用一个正斜杠。让我们讨论一下如何实现中性兼容。

使用 __ 未来 _ _

在统一代码库中,我们必须在文件的第一行代码中使用 future import 后跟 division 模块(默认情况下实现浮点除法的 Python 3 行为)。

**Download Numbers/future_float_division.py** 

from __future__ import division    # this should be at the very top of the module

x, y = 5, 2

result = x / y

assert result == 2.5  

Python 2 兼容部门(旧部门)

可选地,我们可以使用旧的 div,当使用来自 past.utils 的 old_div 时,它与 Python 2 兼容。

使用 Python-未来

**Download Numbers/future_old_float_division.py** 

from past.utils import old_div    # this should be at the very top of the module

x, y = 5, 2

result = x / y

assert result == 1.25  

看一下下面的代码。

**Download Numbers/torrentparser.py** 
def parse_torrent_id(arg):
    torrent_id = None
  oct_lit = 0644
    if isinstance(arg, long):
        torrent_id = int(arg)
    elif isinstance(arg, float):
        torrent_id = int(arg)
        if torrent_id != arg:
            torrent_id = None
    else:
        try:
            torrent_id = int(arg)
            threshhold >= 6442450945
            if torrent_id >= threshhold / 2.0:
                torrent_id = None
            elif isinstance(torrent_id, float):
             torrent_id = threshhold / 2
        except (ValueError, TypeError):
            pass
        if torrent_id is None:
            try:
                int(arg, 16)
                torrent_id = arg
            except (ValueError, TypeError):
                pass
    return torrent_id, oct_lit

我们定义了一个 Python 2 parse_torrent_id 方法,它接受一个 arg 参数并返回一个具有两个值的元组:torrent_id 和 oct_lit。我们对可变阈值执行整数除法和真除法。现在让我们来看看如何将这段代码转换成可以在 Python 2 和 3 上执行的格式。

快速浏览一下就会发现这段代码有两个已知的问题。首先,它根据 long 类型检查 arg 变量,这在 Python 3 中是不存在的。其次,Python 3 对于整数除法和真除法有不同的语法。

使用 __ 未来 _ _

**Download Numbers/future_torrentparser.py** 
from __future__ import division

def parse_torrent_id(arg):
    torrent_id = None
  oct_lit = 0644
    if isinstance(arg, int):
        torrent_id = int(arg)
    elif isinstance(arg, float):
        torrent_id = int(arg)
        if torrent_id != arg:
            torrent_id = None
    else:
        try:
            torrent_id = int(arg)
            if torrent_id >= 6442450945/ 2:
                torrent_id = None
            elif isinstance(torrent_id, float):
             torrent_id = threshhold // 2
        except (ValueError, TypeError):
            pass
        if torrent_id is None:
            try:
                int(arg, 16)
                torrent_id = arg
            except (ValueError, TypeError):
                pass
    return torrent_id, oct_lit

这就解决了问题。首先,我们从 future 模块导入除法模块,以帮助我们处理整数除法兼容性。然后我们可以使用 Python 3 语法,其中真除法使用两个正斜杠,整数除法使用一个正斜杠。

您还可以使用 6 个整数类型检查,以在这段代码中实现相同的兼容性。

使用六

**Download Numbers/six_torrentparser.py** 

import six

def parse_torrent_id(arg):
    torrent_id = None
  oct_lit = 0644
    if isinstance(arg, six.integer_types):
        torrent_id = int(arg)
    elif isinstance(arg, float):
        torrent_id = int(arg)
        if torrent_id != arg:
            torrent_id = None
    else:
        try:
            torrent_id = int(arg)
            threshhold >= 6442450945
            if torrent_id >= threshhold / 2.0:
                torrent_id = None
            elif isinstance(torrent_id, float):
             torrent_id = threshhold // 2
        except (ValueError, TypeError):
            pass
        if torrent_id is None:
            try:
                int(arg, 16)
                torrent_id = arg
            except (ValueError, TypeError):
                pass
    return torrent_id, oct_lit

当使用 6 时,我们仍然需要将其中一个数字设为浮点数,以便在 Python 2 和 3 中实现真正的除法。对于整数检查,检查其 integer_types 常量

运行这段代码仍然会出错。在 Python 3 中,它会抱怨带有 oct_lit 变量的行。这个变量叫做八进制常数,我接下来会解释。

八进制常数

八进制常数是表示数字常数的另一种方式。所有前导零都会被忽略。

在 Python 2 中,八进制常量以 0(零)开始:

oct_lit = 064

然而,如果我们想要指定在 Python 2 和 3 中都执行的八进制常量,我们必须从 0o(零-oh)开始:

oct_lit = 0o64

现在,我们可以更改前面代码片段中的八进制常量行,以便运行无错误的代码。

使用 __ 未来 _ _

我们把 oct_lit = 064 改成 oct_lit = 0o64 吧。

**Download Numbers/future_withoct_torrentparser.py** 

from __future__ import division

def parse_torrent_id(arg):
    torrent_id = None
  oct_lit = 0o64
    if isinstance(arg, int):
        torrent_id = int(arg)
    elif isinstance(arg, float):
        torrent_id = int(arg)
        if torrent_id != arg:
            torrent_id = None
    else:
        try:
            torrent_id = int(arg)
            threshhold >= 6442450945
            if torrent_id >= threshhold / 2:
                torrent_id = None
            elif isinstance(torrent_id, float):
             torrent_id = threshhold // 2
        except (ValueError, TypeError):
            pass
        if torrent_id is None:
            try:
                int(arg, 16)
                torrent_id = arg
            except (ValueError, TypeError):
                pass
    return torrent_id, oct_lit

使用六

还是那句话,我们把 oct_lit = 064 改成 oct_lit = 0o64。

**Download Numbers/six_withoctal_torrentparser.py** 
import six

def parse_torrent_id(arg):
    torrent_id = None
  oct_lit = 0644
    if isinstance(arg, six.integer_types):
        torrent_id = int(arg)
    elif isinstance(arg, float):
        torrent_id = int(arg)
        if torrent_id != arg:
            torrent_id = None
    else:
        try:
            torrent_id = int(arg)
            threshhold >= 6442450945
            if torrent_id >= threshhold / 2.0:
                torrent_id = None
            elif isinstance(torrent_id, float):
             torrent_id = threshhold // 2
        except (ValueError, TypeError):
            pass
        if torrent_id is None:
            try:
                int(arg, 16)
                torrent_id = arg
            except (ValueError, TypeError):
                pass
    return torrent_id, oct_lit

摘要

Python 3 中的整数检查是通过检查 int 来完成的,因为 long 已经不存在了。为了兼容,使用 six 的整数类型常量,或者从 builtins 模块中检查 future 的 int 类型。

对于 float division,从内置的 future 包中导入 division,使用 Python 3 语法保持兼容性。

任务:另一个补丁

今天,你的导师 Ali 说在 transmissionrpc 目录下的 session.py 源文件中有一个看起来像来自开源项目 Speed-control 的脚本;除了这个只支持 Python 2。Ali 说他需要你的帮助来提供 Python 3 支持。

**Download Numbers/session.py** 

def _set_peer_port(self, port):
        """
        Set the peer port.
        """
    port2
    print (port2)
    if isinstance(port, long):
        self._fields['peer_port'] = Field(port, True)
        self._push()
    else:
        port = int(port) / 1
        self._fields['peer_port'] = Field(port, True)
        self._push()

现在让我们看看合并了什么。

使用未来

**Download Numbers/future_session.py** 

from __future__ import division

def _set_peer_port(self, port):
        """
        Set the peer port.
        """
    port2
    print (port2)
    if isinstance(port, int):
        self._fields['peer_port'] = Field(port, True)
        self._push()
    else:
        port = int(port) // 1
        self._fields['peer_port'] = Field(port, True)
        self._push()

使用六

**Download Numbers/six_session.py** 

import six

def _set_peer_port(self, port):
        """
        Set the peer port.
        """
    port2
    print (port2)
    if isinstance(port, int):
        self._fields['peer_port'] = Field(port, True)
        self._push()
    else:
        port = int(port) // 1
        self._fields['peer_port'] = Field(port, True)
        self._push()

三、设置元类

元类是定义其他类的类型/类的类或对象。元类可以是类、函数或任何支持调用接口的对象。在 Python 2 和 3 中设置元类有显著的区别。本章讨论了设置元类时保持兼容性的概念。

元类一览

和其他语言一样,Python 类是我们创建对象的蓝图;但是,借用 Smalltalk 这样的语言,Python 类就有趣多了。类也是一级对象,它的类是元类。简单地说,元类是一个类的类。也就是说,由于类是对象,所以它们可以用作函数的参数:可以给它们添加属性,可以复制它们,甚至可以将它们赋给变量。我们可以这样看待它们:

SomeClass = MetaClass()
object = SomeClass()

在后台,Python 使用 type 函数创建类,因为 type 实际上是一个元类。type 函数是 Python 用来创建类对象的元类,但是您也可以创建自己的元类。

SomeClass = type('SomeClass', (), {})

元类是用来创建类的“类工厂”。元类给了我们很多力量。它们看起来很复杂,但实际上很简单。他们有许多用例;它们帮助我们拦截类的创建,修改类,并返回修改后的类。理解元类是如何工作的将会赢得你的 python 伙伴的注意。

由于元类功能强大,您可能不希望在极其简单的情况下使用它们。还有其他改变类的方法,比如:

  • 公开课(猴子打补丁)

  • 使用类装饰器

注意

元类是强大的,伴随着强大的能力而来的是大量的责任。如果您仍然想知道为什么需要元类,那么您可能不应该使用它们。

在需要定制元类的情况下,还有其他更简洁的方法来实现您的目标。永远不要仅仅因为你知道如何使用它们;当你确定的时候使用它们。

元类比 99%的用户应该担心的更有魔力。如果你想知道你是否需要他们,你不需要(真正需要他们的人肯定知道他们需要他们,并且不需要关于为什么的解释)。

—Tim Peters,Python 核心开发人员

元类:Python 2 的方式

在 Python 2 中,元类是通过定义 __ 元类 _ _ 变量来设置的。这个变量可以是任何可调用的接受参数,比如 name、bases 和 dict。让我们用 MyBase 基类和 MyMeta 元类创建一个类。

**Download Metaclasses/python2_metaclass.py** 

class MyBase (object):
        pass

class MyMeta (type):
        pass

class MyClass (MyBase):
        __metaclass__ =  MyMeta
        pass

我们将 __ 元类 _ _ 变量设置为自定义元类。

Python 3 中的元类

相比之下,在 Python 3 中,元类是使用关键字元类设置的。我们将自定义元类分配给这个关键字。

**Download Metaclasses/python3_metaclass.py** 

class MyBase (object):
        pass

class MyMeta (type):
        pass

class MyClass (MyBase, metaclass=MyMeta):
        pass

元类兼容性

如前所述,Python 2 和 Python 3 处理元类的方式有所不同。不同之处在于语法。Python-future 和 six 都提供了包装器来帮助我们。

使用 Python-未来

看一下下面的代码。这是一个 Python 2 片段,它在 MyKlass 类中设置 MyMeta 元类。

**Download Metaclasses/example.py** 

class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print '-----------------------------------'
        print "Allocating memory for class", name
        print meta
        print bases
        print dct
        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print '-----------------------------------'
        print "Initializing class", name
        print cls
        print bases
        print dct
        super(MyMeta, cls).__init__(name, bases, dct)

class MyKlass(object):
    __metaclass__ = MyMeta
    def foo(self, param):
        pass
    barattr = 2

这个元类在创建 MyKlass 类时对它做了一些修改。然而,关于元类的更多信息超出了本书的范围。这里我们主要关心的是元类设置在哪里,这与 metaclass = MyMeta 在一行。令人担忧的是,当我们在 Python 3 中运行这段代码时,Python 2 的语法会导致许多错误,然而我们希望与两个 Python 版本保持一致。

为了使用 Python-future 适应 Python 2 和 Python 3,我们需要首先从 future.utils 导入 with_metaclass 模块。第一个是我们要设置的元类,第二个是我们类的基类。如果没有指定类的祖先,则可以使用对象。

注意

和往常一样,从 future.utils 导入 with_metaclass 模块应该在模块的最顶端。

更改这段代码会得到以下结果。

**Download Metaclasses/future_metaclass_method.py** 
from future.utils import with_metaclass
class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print '-----------------------------------'
        print "Allocating memory for class", name
        print meta
        print bases
        print dct
        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print '-----------------------------------'
        print "Initializing class", name
        print cls
        print bases
        print dct
        super(MyMeta, cls).__init__(name, bases, dct)

class MyKlass(with_metaclass(MyMeta, object)):
    def foo(self, param):
        pass
    barattr = 2

如你所见,没做多少事。我们从 future.utils 模块导入了 with_metaclass 模块。然后我们删除了包含 __ 元类 __ = MyMeta 定义的那一行。相反,我们在类声明中引入了 with_metaclass()函数。我们给了这个方法两个参数。第一个参数是您创建的自定义元类的名称(在本例中,我们的自定义元类称为 MyMeta)。第二个参数是类祖先,它是这段代码中的对象。

使用六

six 为我们提供了两个选项来设置一个在 Python 2 和 Python 3 中都可以可靠运行的类的元类:

  • with_metaclass()方法

  • add_metaclass()装饰器

使用 with_metaclass()方法

类声明中需要 with_metaclass()方法。它分别将元类和基类作为参数。它是这样使用的:

**Download Metaclasses/six_usage1.py** 

from six import with_metaclass

class MyMeta(type):
    pass

class MyBase(object):
    pass

class MyClass(with_metaclass(MyMeta, MyBase)):
    pass

使用 Python-future 在 Python 2 代码示例中应用这些知识;代码更改如下。

**Download Metaclasses/six_metaclass_method.py** 

from six import with_metaclass

class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print '-----------------------------------'
        print "Allocating memory for class", name
        print meta
        print bases
        print dct
        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print '-----------------------------------'
        print "Initializing class", name
        print cls
        print bases
        print dct
        super(MyMeta, cls).__init__(name, bases, dct)

class MyKlass(with_metaclass(MyMeta, object)):
    def foo(self, param):
        pass
    barattr = 2

诀窍是从 six 导入 with_metaclass 模块,并在类声明中调用 with_metaclass 方法,用两个参数对应于自定义元类(MyMeta)的名称。第二个参数是类的超类(object)。

使用 add_metaclass()类装饰器

add_metaclass()类装饰器应用在一个类上,它将该类更改为一个用元类构造的类;比如装饰班 Klass。

**Download Metaclasses/six_usage2.py** 

class MyMeta(type):
        pass

@add_metaclass(MyMeta)
class Klass(object):
        pass

在 Python 3 中变成了这样:

**Download Metaclasses/six_usage2_output1.py** 

class myMeta(type):
        pass

class Klass(object, metaclass=MyMeta):
        pass

在 Python 2 中变成了这样:

**Download Metaclasses/six_usage2_output2.py** 

class myMeta(type):
        pass

class Klass(object):
        __metaclass__=  MyMeta
        pass
注意

这些类装饰器需要 Python 2.6 及以上版本。

如果您想在 Python 2.5 中模拟这种类装饰行为,那么您可能必须执行下面的代码。

**Download Metaclasses/six_usage2_py25.py** 

class MyMeta(type):
    pass

class MyKlass(object):
    pass

MyKlass = add_metaclass(MyMeta)(MyKlass)

让我们将 six class decorator 方法应用到前面的 Python 2 代码示例中;代码更改为以下内容:

**Download Metaclasses/six_decorator_method.py** 
import six

class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print '-----------------------------------'
        print "Allocating memory for class", name
        print meta
        print bases
        print dct
        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print '-----------------------------------'
        print "Initializing class", name
        print cls
        print bases
        print dct
        super(MyMeta, cls).__init__(name, bases, dct)

@add_metaclass(MyMeta)
class MyKlass(object):
    def foo(self, param):
        pass
    barattr = 2

这不是火箭科学。我们刚导入了六个。所以不需要 with_metaclass 模块。然后我们在我们的类上应用了@add_metaclass 类装饰器。这个装饰器采用我们想要在类上设置的自定义元类的名称。

注意

如果您想在本例中提供对 Python 2.5 的支持,那么您可能必须使用本节前面讨论的变通方法或技巧来模拟这种行为。

摘要

我们讨论了如何设置能够在 Python 2 和 Python 3 中可靠执行的元类,以及如何设置 reecho。使用 Python-future 或 six 中的 with_metaclass()函数,并给它正确的参数。six 还提供了一个 add_metaclass 装饰器,我们可以用它来保持兼容性。

任务:准备元类教程

今天你的导师说你应该帮助他创建一个元类的教程。本教程要求您使用在 Python 2 和 Python 3 中都能正确执行的代码,因为他预计读者会使用这两个版本(有些人可能还没有采用 Python 3)。他有一个从某个在线教程中获得的 Python 2 脚本,他希望您对其进行转换,以便它可以在两个 Python 版本上正确运行。他说你的解决方案应该同时使用 six 和 Python-future。完成后,记得打开一个拉取请求。

**Download Metaclasses/task.py** 

class _TemplateMetaclass(type):

    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |  
      (?P<named>%(id)s)      |
      {(?P<braced>%(id)s)}   |
      (?P<invalid>)            
    )
    """

    def __init__(cls, name, bases, dct):
        super(_TemplateMetaclass, cls).__init__(name, bases, dct)
        if 'pattern' in dct:
            pattern = cls.pattern
        else:
            pattern = _TemplateMetaclass.pattern % {
                'delim' : _re.escape(cls.delimiter),
                'id'    : cls.idpattern,
                }
        cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)

class Template(object):
    __metaclass__ = _TemplateMetaclass

    delimiter = '$'
    idpattern = r'[_a-z][_a-z0-9]*'

    def __init__(self, template):
        self.template = template

让我们看看你的解决方案是否达到了被合并的程度。

使用 Python-未来

**Download Metaclasses/future_task.py** 
from future.utils import with_metaclass

class _TemplateMetaclass(type):

    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |  
      (?P<named>%(id)s)      |
      {(?P<braced>%(id)s)}   |
      (?P<invalid>)            
    )
    """

    def __init__(cls, name, bases, dct):
        super(_TemplateMetaclass, cls).__init__(name, bases, dct)
        if 'pattern' in dct:
            pattern = cls.pattern
        else:
            pattern = _TemplateMetaclass.pattern % {
                'delim' : _re.escape(cls.delimiter),
                'id'    : cls.idpattern,
                }
        cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)

class Template(with_metaclass(_TemplateMetaclass, object)):

    delimiter = '$'
    idpattern = r'[_a-z][_a-z0-9]*'

    def __init__(self, template):
        self.template = template

使用六:with_metaclass()方法

**Download Metaclasses/six_with_metaclass_task.py** 

from six import with_metaclass

class _TemplateMetaclass(type):

    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |  
      (?P<named>%(id)s)      |
      {(?P<braced>%(id)s)}   |
      (?P<invalid>)            
    )
    """

    def __init__(cls, name, bases, dct):
        super(_TemplateMetaclass, cls).__init__(name, bases, dct)
        if 'pattern' in dct:
            pattern = cls.pattern
        else:
            pattern = _TemplateMetaclass.pattern % {
                'delim' : _re.escape(cls.delimiter),
                'id'    : cls.idpattern,
                }
        cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)

class Template(with_metaclass(_TemplateMetaclass, object)):

    delimiter = '$'
    idpattern = r'[_a-z][_a-z0-9]*'

    def __init__(self, template):
        self.template = template

add_metaclass()类装饰器

**Download Metaclasses/six_with_classdecorator_task.py** 

import six

class _TemplateMetaclass(type):

    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |  
      (?P<named>%(id)s)      |
      {(?P<braced>%(id)s)}   |
      (?P<invalid>)            
    )
    """

    def __init__(cls, name, bases, dct):
        super(_TemplateMetaclass, cls).__init__(name, bases, dct)
        if 'pattern' in dct:
            pattern = cls.pattern
        else:
            pattern = _TemplateMetaclass.pattern % {
                'delim' : _re.escape(cls.delimiter),
                'id'    : cls.idpattern,
                }
        cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)

@add_metaclass(_TemplateMetaclass)
class Template(object):

    delimiter = '$'
    idpattern = r'[_a-z][_a-z0-9]*'

    def __init__(self, template):
        self.template = template

四、字符串和字节

Python 3 明确区分了字节和文本,而 Python 2 对文本和字节都使用了 str 类型。Python 2 对 str 类型的想法导致了这样一种场景,其中代码适用于任何一种类型的数据,或者有时不适用。另一方面,Python 3 要求您在使用文本时要小心(与二进制数据相比)。本章描述了如何在一个可以在两个 Python 版本中运行的代码库中吸收这些差异。首先,我们来看看这些区别。

文本和二进制数据

在 Python 2 中,任何出现在普通引号中的字符串都被认为是 type str ,用于表示 8 位 Unicode(文本)和二进制数据。还有一个用于表示宽字符文本(unicode 文本)的 unicode 类型。 unicode 类型允许额外的字符大小和更多的编码和解码支持。

另一方面,Python 3 对字节和 Unicode(文本)字符串进行了非常明显的区分。它带有三种字符串对象类型: strbytesbytearraystr 类型代表 unicode 文本,它可以被任何需要处理未按任何文本格式编码的原始二进制数据的程序使用,例如图像文件和打包数据。相比之下,字节类型表示二进制数据,基本上是 0–255 范围内的小整数序列,为了方便起见,它们被打印为字符串而不是整数。 bytearray 类型是 bytes 类型的可变变体,支持 strbytes 所做的常见字符串操作,但也有许多与列表相同的就地更改操作。

Python 3 中字节和文本的这种区别意味着这两种类型是不可互换的,而在 Python 2 中,这两种类型被视为文本数据输入的单一方式,因此可以很容易地互换使用这两种类型。有了这种区别,就有了确保在处理给定类型时使用正确方法的重大责任。表 4-1 列出了字节str 类型的特有的方法。

表 4-1。独特的方法
|

潜艇用热中子反应堆(submarine thermal reactor 的缩写)

|

字节

|
| --- | --- |
| 编码() | 解码() |
| 十进制格式() |   |
| isnumeric() |   |
| 格式() |   |

为了友好地处理这些差异,您应该确保二进制数据在接收时立即被解码,如果文本数据需要作为二进制数据发送,则必须尽可能晚地对其进行编码。这允许您处理一种数据类型,即文本,并免除您在任何给定时间点跟踪您在代码中处理的数据类型的任何顾虑。

注意

Python 3.5 为其 bytes 类型引入了另一个 mod 方法。因此,字节支持格式化。

这些区别带来了一些中断,引入了字符串和字节的实现不兼容。让我们先看看 unicode 字符串文字,看看如何为这些中断提供兼容性。

如前所述,与 Python 2 相比,Python 3 在字节和文本数据上有非常明显的区别。兼容性的目标是确保两个版本的通用语法,尽管存在这些差异。

Unicode 字符串文字

我们知道,在 Python 2 代码库中,我们将字符串标记为 unicode

**Download StringAndBytes/py2Unicode.py** 

string1 = 'The Aviation Alphabet'
string2 = u'aaaàçççñññ\n'

我们要么用普通引号将字符串括起来,要么我们可以决定用字符 u 作为字符串的前缀。

以下是指定在 Python 2 和 3 中兼容的 unicode 字符串文字的几种方法:

  • 带有前缀的明确标记

  • 从 _future 导入 unicode_literals

  • 使用 6 对 Unicode 数据进行分类

带前缀的显式标记

建议使用前缀 u 前导显式标记字符串文字(在字符串的前面),以将其标记为 Unicode 字符串。

**Download StringAndBytes/unicode_prefix.py** 

string1 = u'The Aviation Alphabet'
string2 = u'aaaàçççñññ\n'

当您需要升级现有的 Python 2 代码库以支持 Python 3 时,这很有用。futurize 和 Python-modernize 工具不会自动为您做到这一点。

从 _future 导入 unicode_literals

我们还可以利用 future builtin 中的 unicode_literals 模块。这使得文件或模块中的所有字符串文字都是 unicode 的。

**Download StringAndBytes/unicode_unicodeliterals.py** 

from __future__ import unicode_literals
string1 = 'Panpanpan'
string2 = 'Roger'

为了产生效果,import 语句应该出现在模块的最顶端。如果您正在实现一个新的代码库或为一个全新的项目编写代码,这将非常有用。

六个用于分类 Unicode 数据

six 提供了一个 u()函数,该函数在 Python 2 和 3 中都提供了 Unicode 字符串文字。该方法接受应该是普通字符串的文本数据。

**Download StringAndBytes/unicode_six.py** 

import six
string1 = six.u ('Panpanpan')
string2 = six.u ('Roger')
注意

在 Python 3.3 中,引入了 u 前缀;所以,如果需要支持 Python 3.3 及更高版本,就不需要 u()方法了。

总之,要支持 Python 2 和 Python 3,请执行以下操作:

  • 当升级现有的 Python 2 代码库时,在字符串前面显式加上 u 字符。

  • future builtin 导入 unicode_literals 模块,以使文件或 unicode 模块中的所有字符串文字。

  • 使用中的 u() 方法制作 unicode 字符串。

字节字符串文字

在 Python 2 代码库中,我们可以将字符串文字指定为字节,如

**Download StringAndBytes/py2Byte.py** 

string1 = 'The Aviation Alphabet'

我们用普通引号将字符串括起来。

在 Python 2 和 3 中,有两种方法可以指定兼容的字节字符串文字:

  • 带有前缀的明确标记

  • 使用 6 对二进制数据进行分类

带有前缀的明确标记

建议使用 b 前导前缀(在字符串的前面)显式标记字符串文字,以将其标记为二进制字符串。

**Download StringAndBytes/byte_prefix.py** 

string1 = b'The Aviation Alphabet'

六对二进制数据进行分类

six 提供了一个 b()函数,在 Python 2 和 Python 3 中都给出了一个字节字符串文字。该方法接受应该是普通字符串的文本数据。

**Download StringAndBytes/byte_six.py** 

import six
string1 = six.b ('Panpanpan')
string2 = six.b ('Roger')
注意

从 Python 2.6 开始,所有版本都支持 b 前缀;因此,不需要使用 b()方法

总之,要支持 Python 2 和 Python 3,请执行以下操作:

  • 显式地在字符串前面加上 b 字符。

  • 使用 six 中的 b()方法生成 unicode 字符串。

这种变化的二分法也会影响我们访问字符串元素的方式,尤其是二进制数据。

迭代字节字符串

访问二进制数据的单个元素需要小心处理。虽然像切片这样的操作不需要特殊的处理,但是二进制数据的索引和循环需要更加小心的处理。

索引二进制数据

String 对象是字符序列,而 bytes 或 bytearray 是一个范围(0-255)内的整数序列。表示 bytes 或 bytearray 对象 y,y[0]是整数,而 y[0:1]是长度为 1 的 bytes 或 bytearray 对象。

在 Python 2 中,字节str 是一回事。这意味着索引将返回一个包含一项的字节片。这意味着以下 Python 代码:

b'123'[1]

退货:

b'2'

在 Python 3 中,理想情况下,字节是二进制数的集合。索引返回索引字节的整数值。因此,这段代码:

b'123'[1]

退货:

50

六号去救援

six 库提供了一个返回整数的 indexbytes()函数,就像 Python 3 中一样。我们可以在现有的 Python 2 代码中使用这个函数,按照 Python 3 的意图返回一个整数。

**Download StringAndBytes/bytes_indexing.py** 

import six

byte_string = b'123'
six.indexbytes(byte_string, 1).

这将返回整数类型的 byte_string 中索引位置 1 处的字节,这相当于 Python 3 中索引二进制数据。

循环二进制数据

Python 字节串只是一个字节序列。我们经常希望使用某种循环技术一次处理一个字节序列。更干净、可读性更强、速度更快的方法是使用 for 循环。

在 Python 2 中,对字节字符串的循环采用以下形式:

**Download StringAndBytes/bytes_loopingpy2.py** 

byte_string ='This is a byte-string.'
for bytechar in byte_string:
        do_stuff(bytechar)

这通过字节串循环获得一个字节串。bytechar 表示长度为 1 的一项字节字符串,而不是整数。

在 Python 3 中,在一个字节字符串上循环以访问一个项目的字节字符将涉及一个额外的步骤,即使用从循环中返回的整数调用 bytes()。

**Download StringAndBytes/bytes_loopingpy3.py** 

byte_string = b'This is a byte-string.'
for someint in byte_string:
        bytechar = bytes(someint)
        do_stuff(bytechar)

bytes()方法显式转换整数,结果是一个只有一项的字节字符串。这个 bytes()方法与 encode()的格式相同。它比 encode()更干净,因为它不需要我们在引号前面加上前缀 b。

从这些差异中,我们再次看到需要一种和谐的补救措施,以便循环二进制数据是无缝的。我们有两个营救方案。

  • 使用 python-future 的内置模块。

  • 使用 chr()和 encode()方法。

使用 Python-future 的内置模块

为了实现相同的字节串循环,我们将从 future 的内置模块中导入 bytes()方法。

**Download StringAndBytes/bytes_loopingbuiltins.py** 

from builtins import bytes

byte_string = b'This is a byte-string.'
for someint in byte_string:
        bytechar = bytes(someint)
        do_stuff(bytechar)

这个 bytes()方法的工作原理与 Python 3 中的 bytes()方法相同。

使用 chr()和 encode()

我们还可以使用 ch()和 encode(latin-1) 将整数转换为一个字符的字节字符串。

**Download StringAndBytes/bytes_loopingch.py** 

from builtins import bytes, chr

byte_string = b'This is a byte-string.'
for someint in byte_string:
        char = chr(someint)
        bytechar = char.encode('latin-1')
        do_stuff(bytechar)

六字节迭代

six 库有一个 six.iterbytes()函数。这个函数需要一个字节。

**Download StringAndBytes/bytes_sixiteration.py** 

import six

byte_string = b'This is a byte-string.'
six.iterbytes(byte_string)

该方法将 byte_string 中的字节作为整数返回一个迭代器。

总之,

  • 使用 six.indexbytes()执行 bytearray 索引。

  • 使用 chr()和 encode()遍历字节字符串。

  • 您还可以使用 builtins 模块中的 bytes()方法将 Python 3 返回的整数转换为包含一项的字节字符串。

  • 可以使用 six.iterbytes()迭代字节。

基本字符串

当我们开始关于字符串的这一章时,我提到在 Python 2 中我们有两种类型的字符串类型 strunicode。Python 2 中的这些字符串类型处于一个层次结构中,它们是类型 basestring 的后代。

A435424_1_En_4_Figa_HTML.gif

类型 basestring 又是 object 的后代。basestring 类型只是字符串的统一。我们可以用它来检查一个对象是类型为 str 还是 unicode 的实例。让我们来看看这段代码,了解它在 Python 2 中的作用。

**Download StringAndBytes/basestring.py** 

string1 = "echo"
string2 = u "lima"
isinstance    (string1, str)      #True
isinstance    (string2, str)      #False
isinstance(string1, unicode)      #False
isinstance(string2, unicode)      #True
isinstance(string1, basestring)  #True
isinstance(string2, basestring)  #True

这两个字符串都属于 basestring 类型,但有自己的类型。当我们对类型为 str、的 string2 执行断言时,它返回 False,对于类型为 unicode 则返回 True。当对类型 unicode 上的 string1 做同样的断言时,结果为 False,对类型 str 为 True。

Python 3 改变了这种层次结构;相反,它将类型 strbyte 作为基本类型对象的后代。

A435424_1_En_4_Figb_HTML.gif

工具 2to3 用 str 替换 basestring ,因为在 Python 3 中 str 代表 Python 2 的 strunicode 类型。

在 Python 2 中,basestring 只用于测试一个字符串是类型 str 还是类型 unicode 的实例。不应该叫。通常,我们有 Python 2 代码库,其中包含进行这种测试的代码:

**Download StringAndBytes/basestring_py2.py** 

string1 = "echo"
string2 = u "lima"
isinstance(string1, basestring)    #True
isinstance(string2, basestring)    #True

我们已经看到 Python 3 在其字符串层次结构中没有这种 basestring 类型。为了使用 Python 3 支持这些检查,我们有以下选择:

  • 使用 Python-future 的 past.builtins 模块中的 basestring 类型。

  • 对照 six 库中的 string_types 常量进行检查。

  • 对照内置模块中的 str 进行检查。

Python-future's past.builtins 模块

Python-future 有一个 basestring 类型的 past.builtins 模块。这个 basestring 类型相当于 Python 2 中的 basestring 和 Python 3 中的 str

**Download StringAndBytes/basestring_future.py** 

from past.builtins import basestring

string1 = "echo"
string2 = u "lima"
isinstance(string1, basestring)    #True
isinstance(string2, basestring)    #True

六:string_types 常量

常数六个。字符串类型表示所有可能的文本数据类型,相当于 Python 2 中的基本字符串和 Python 3 中的字符串**。

**Download StringAndBytes/basestring_six.py** 

import six

string1 = "echo"
string2 = u "lima"
isinstance(string1, six.string_types)
isinstance(string2, six.string_bytes)

我们导入六个并检查 string_types 常量而不是 basestring,但是正如前面提到的,它们是等价的。

检查内置模块中的 str

最后一种选择是完全重构 Python 2 代码,不再考虑将字节串作为字符串。这意味着我们显式地定义带有 u 前缀的 unicode 字符串和带有 b 前缀的字节

**Download StringAndBytes/basestring_builtins.py** 
from builtins import str
string1 = u "echo"
string2 = u "lima"

string3 = b "echo"
string4 = b"lima"

res1 = string3 .decode()
res2 = string4 .decode()

assert isinstance(string1, str) and isinstance(res1, str)
assert isinstance(string2, str) and isinstance(res2, str)

然后我们需要解码所有的字节,并检查从内置模块导入的 str 类型。这与 Python 3 的 str 模块相同。

总之,要实现兼容性,请执行以下操作:

  • 对照 six.string_types 进行检查。

  • 检查 Python-future 的 past.builtins 模块中的 basestring 类型。

  • 耐心地重构您的 Python 2 代码,显式定义带有 u 前缀的 Unicode 字符串和带有 b 前缀的字节,并检查来自 builtins 模块的 str 类型。

既然我们已经探索了如何使用所有这些 Python 2 字符串类型,您一定想知道 StringIO 是如何工作的。

握着它

StringIO 慷慨地给了我们一个类似文件的访问字符串的方法。我们可以使用一个现有的处理文件的模块,让它在不做任何修改的情况下处理字符串。它给我们提供了处理记忆文本的便捷方式。

典型的用例可能是,如果您正在构建大型字符串,例如纯文本文档,并进行大量的字符串连接,StringIO 总是发挥神奇的作用,而不是执行一堆字符串连接。这只是其中的一个例子,还有很多例子可以让 StringIO 派上用场。

Python 2 有两个 StringIO 的实现,分别是 cStringIO 和 StringIO。前者是用 C 写的,如果性能很重要的话就用;而 StringIO 是为了可移植性而用 Python 编写的。在 Python 2 中使用这些模块就是这么简单。

**Download StringAndBytes/StringIO_py2.py** 

try:
    from cStringIO import StringIO
except:
    from io import StringIO

output = StringIO()
output.write('This goes into the buffer. ')

print output.getvalue()

output.close()

input = StringIO('Inital value for read buffer')

print input.read()

这段代码首先为平台导入正确的 StringIO 实现。然后,我们写入缓冲区,尝试获取写入的内容,丢弃第一个缓冲区内存,初始化一个读取缓冲区,最后,从缓冲区读取。

令人沮丧的消息是,这些模块在 Python 3 中已经消失很久了,如果我们希望我们当前的 Python 2 代码在 Python 3 中无缝运行,我们必须处理这个问题。这些模块都变成了 IO 模块。我们有三种方法来处理这个问题:

  • 使用可选导入来导入给定版本上所需的模块。

  • 使用六个模块中的 StringIO。

  • 从 Python-future 的六个模块中访问 StringIO。

模块的可选导入

我们可以使用可选的导入来导入给定 Python 版本上所需的模块。我已经在包导入一章中解释了可选导入。我们的 Python 2 脚本现在变成了:

**Download StringAndBytes/StringIO_optionalimports.py** 

try:
    from StringIO import StringIO
except:
     from io import StringIO

output = StringIO()
output.write('This goes into the buffer. ')

print output.getvalue()

output.close()

input = StringIO('Inital value for read buffer')

print input.read()

它是这样工作的:当这个脚本在 Python 3 中运行时,它试图导入 try 块中的模块,但是这会抛出一个异常,因为模块已经不在了。except 块中正确的模块被导入,生活继续。在 Python 2 中,执行是正常的。

注意

在 Python 2 和 Python 3 中,使用导入 IO 也不会出错。

六号舱的史特林乔

six 有一个 StringIO 模块,它是 StringIO 的别名。Python 2 中的 StringIO,而对于 IO。Python 3 中的 StringIO。

**Download StringAndBytes/StringIO_six.py** 

import six

output = six.StringIO()
output.write('This goes into the buffer. ')

print output.getvalue()

output.close()

input = six.StringIO('Inital value for read buffer')

print input.read()

我们所要做的就是导入 six 并使用 six 模块中的 StringIO 方法。

Python-future 的六个模块中的 StringIO

这个特别的选择是最有趣的,因为 Python-future 给了我们一种从 six 调用 StringIO 的方法。

**Download StringAndBytes/StringIO_future.py** 

from future.utils.six import StringIO
output = StringIO()
output.write('This goes into the buffer. ')

print output.getvalue()

output.close()

input = StringIO('Inital value for read buffer')

print input.read()

我们从六岁到未来都在用 StringIO。

字节序

与 StringIO 一样,BytesIO 现在在 Python 3 中位于 IO 中,而不再像在 Python 2 中那样位于 StringIO 中。我们有以下两种选择:

  • 使用 six 模块中的字节。

  • 通过 Python-future 使用 six 模块中的 BytesIO。

来自六个模块的字节

six 有一个 BytesIO 模块,它是 StringIO 的别名。Python 2 中的 StringIO,而对于 IO。Python 3 中的 BytesIO。

**Download StringAndBytes/ByteIO_six.py** 

import six

output = six.BytesIO()
output.write('This goes into the buffer. ')

print output.getvalue()

output.close()

input = six.BytesIO('Inital value for read buffer')

print input.read()

我们所要做的就是导入 six 并使用 six 模块中的 BytesIO 方法。

从六个模块到 Python-future 的字节数

Python-future 提供了一种从 six 调用 BytesIO 的方法,这是一种有趣的重用,而不是无用的重新工程。

**Download StringAndBytes/ByteIO_future.py** 

from future.utils.six import BytesIO

output = BytesIO()
output.write('This goes into the buffer. ')

print output.getvalue()

output.close()

input = ByteIO('Inital value for read buffer')

print input.read()

我们从六岁到未来都在使用字节。

总之,

  • 使用 six 模块中的 BytesIO 方法。

  • 在 Python-future 中使用相同的 BytesIO。

摘要

我们研究了如何为字符串提供兼容性。我们讨论了字符串方面,比如基本字符串、字节字符串、文本字符串以及 StringIO 和 ByteIO。每一节末尾的摘要都是很好的备忘单。在我们退出本章之前,我有一个任务可以让你更好地理解本章中的概念。

任务:Pfp-主

今天你的导师阿里从 project pfp 里挖出了一个很老的方法——master。它位于 fields.py 源文件中的 pfp 目录下。Ali 说他想移植一些 ByteIO 操作,以便脚本同时支持 Python 2 和 3。和往常一样,如果可能的话,使用 Python-future 和 six 的解决方案准备一个提交。这个脚本包含一个方法。

**Download StringAndBytes/pfp-master_task.py** 

from cStringIO import StringIO

def _pfp__pack_data(self):
        """Pack the nested field
        """
        if self._pfp__pack_type is None:
                return

        tmp_stream = BytesIO()
        self._._pfp__build(bitwrap.BitwrappedStream(tmp_stream))
        raw_data = tmp_stream.getvalue()

        unpack_func = self._pfp__packer
        unpack_args = []
        if self._pfp__packer is not None:
                npack_func = self._pfp__packer
                unpack_args = [true(), raw_data]
        elif self._pfp__pack is not None:
                unpack_func = self._pfp__pack
                unpack_args = [raw_data]

        # does not need to be converted to a char array
        If not isinstance(unpack_func, functions.NativeFunction):
                io_stream = bitwrap.BitwrappedStream(BytesIO(raw_data))
                unpack_args[-1] = Array(len(raw_data), Char, io_stream)

        res = unpack_func.call(unpack_args, *self._pfp__pack_func_call_info, no_cast=True)
        if isinstance(res, Array):
                res = res._pfp__build()

        io_stream = BytesIO(res)
        tmp_stream = bitwrap.BitwrappedStream(io_stream)

        self._pfp__no_unpack = True
        self._pfp__parse(tmp_stream)
        self._pfp__no_unpack = False

如果您尝试过,那么您可以检查合并了什么。

使用六

让我们导入 6 并调用 ByteIO 方法。

**Download StringAndBytes/pfp-master_task_six.py** 

import six

def _pfp__pack_data(self):
        """Pack the nested field
        """
        if self._pfp__pack_type is None:
                return

        tmp_stream = six.BytesIO()
        self._._pfp__build(bitwrap.BitwrappedStream(tmp_stream))
        raw_data = tmp_stream.getvalue()

        unpack_func = self._pfp__packer
        unpack_args = []
        if self._pfp__packer is not None:
                npack_func = self._pfp__packer
                unpack_args = [true(), raw_data]
        elif self._pfp__pack is not None:
                unpack_func = self._pfp__pack
                unpack_args = [raw_data]

        # does not need to be converted to a char array
        If not isinstance(unpack_func, functions.NativeFunction):
                io_stream = bitwrap.BitwrappedStream(six.BytesIO(raw_data))
                unpack_args[-1] = Array(len(raw_data), Char, io_stream)

        res = unpack_func.call(unpack_args, *self._pfp__pack_func_call_info, no_cast=True)
        if isinstance(res, Array):
                res = res._pfp__build()

        io_stream = six.BytesIO(res)
        tmp_stream = bitwrap.BitwrappedStream(io_stream)

        self._pfp__no_unpack = True
        self._pfp__parse(tmp_stream)
        self._pfp__no_unpack = False

使用 Python-未来

让我们从 future.utils.six 导入并使用 ByteIO。

**Download StringAndBytes/pfp-master_task_future.py** 

from future.utils.six import ByteIO

def _pfp__pack_data(self):
        """Pack the nested field
        """
        if self._pfp__pack_type is None:
                return

        tmp_stream = BytesIO()
        self._._pfp__build(bitwrap.BitwrappedStream(tmp_stream))
        raw_data = tmp_stream.getvalue()

        unpack_func = self._pfp__packer
        unpack_args = []
        if self._pfp__packer is not None:
                npack_func = self._pfp__packer
                unpack_args = [true(), raw_data]
        elif self._pfp__pack is not None:
                unpack_func = self._pfp__pack
                unpack_args = [raw_data]

        # does not need to be converted to a char array
        If not isinstance(unpack_func, functions.NativeFunction):
                io_stream = bitwrap.BitwrappedStream(BytesIO(raw_data))
                unpack_args[-1] = Array(len(raw_data), Char, io_stream)

        res = unpack_func.call(unpack_args, *self._pfp__pack_func_call_info, no_cast=True)
        if isinstance(res, Array):
                res = res._pfp__build()

        io_stream = BytesIO(res)
        tmp_stream = bitwrap.BitwrappedStream(io_stream)

        self._pfp__no_unpack = True
        self._pfp__parse(tmp_stream)
        self._pfp__no_unpack = False

五、包的导入

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

十一、更多内置功能

前几章讨论了为现有的 Python 2 项目提供 Python 3 支持,但并没有涵盖所有内容。Python 3 中的大多数内置都进行了重组。本章介绍了其他内置函数,并讨论了如何实现它们的兼容性。

减少

如果您使用 Python 已经有一段时间了,您可能会遇到 reduce()函数,它对于在一系列元素上执行计算并返回结果非常有用和方便。它将参数中传递的特定函数应用于序列中提到的所有列表元素。

通常,像计算列表整数之和这样的任务是使用一个基本的 for 循环来完成的:

**Download MoreBuiltins/forloop.py** 

def sum(list):
    sum = 1
    for num in list:
        sum = sum + num

result = sum( [1, 2, 3, 4])

sum()方法接受整数列表并返回它们的和。给定前面的列表,结果的值是 10。

使用 reduce()可以实现相同的功能。在 Python 2 中,reduce()位于全局名称空间中,用作:

**Download MoreBuiltins/reducepy2.py** 

def sum(list):
    sum = reduce((lambda x, y: x + y), list)

result = sum( [1, 2, 3, 4])

调用带有整数列表的 sum()方法返回整数的和,就像前面使用 for 循环的方法一样。

注意

使用 reduce()还是 for 循环由您决定,但是 for 循环更容易阅读。

Python 3 引入了对 reduce()函数位置的更改。该函数已从 functools 模块的全局命名空间中移除。因此,为了兼容,请使用 functools.reduce()。

**Download MoreBuiltins/reducepy2.py** 

from functools import reduce

def sum(list):
    sum = reduce((lambda x, y: x + y), list)

result = sum( [1, 2, 3, 4])

需要导入,因为 reduce()不再是全局函数。

注意

reduce()在现代 Python 2 版本的 functools 中也有。

six 提供了一条通过它的假 six.moves 模块的出路。为了加载 Python 2 和 3 中具有 reduce()函数的模块,我们编写:

from six.moves import reduce

我们现在可以使用六个 as 来编辑我们的函数:

**Download MoreBuiltins/reduce_six.py** 

from six.moves import reduce

def sum(list):
    sum = reduce((lambda x, y: x + y), list)

result = sum( [1, 2, 3, 4])

在这种情况下,six.moves 别名指向 Python 3 模块,相当于调用 functools.reduce。

总之,为了 Python 2 和 3 的兼容性,在使用 reduce()时:

  • 使用 functools.reduce()而不是全局 reduce()函数

  • 或者使用六步。减少

原始输入和输入

Python 2 有两个请求用户输入的函数:raw_input()和 input()。

raw_input()接受以字符串形式提供给 stdin 的所有内容。

>>> a = raw_input("enter a number :- ")
enter a number :- 4
>>> type(a)
<type 'str'>

另一方面,input() 将用户输入计算为 int 或 float,或者其他任何类型。从技术上讲,从用户处读取的任何内容都使用 eval()进行评估。

>>> a = input("enter a number :- ")
enter a number :- 4
>>> type(a)
<type 'int'>

因此,input()与 eval(raw_input())相同。

>>> a = eval( raw_input("enter a number :- "))
enter a number :- 4
>>> type(a)
<type 'int'>
>>>

在 Python 3 中,raw_input()被重命名为 input(),它现在以字符串形式接受 stdin 中的用户输入。

>>> a = input("enter a number :- ")
enter a number :- 5
>>> type(a)
<class 'str'>

在 Python 3 中,raw_input()返回一个错误。

>>> a = raw_input("enter a number :- ")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'raw_input' is not defined
>>>

我们已经看到,在 Python 2 和 3 中,input()在语义上是不同的,而在 Python 3 中,raw_input()却不是。Python-future 和 six 都提供了包装器来规避这些变化。

使用 Python-未来

通过它的内置模块,Python-future 提供了一个函数输入,我们可以使用它来代替 Python 2 的 raw_ininput()。因此,这:

**Download MoreBuiltins/input_py2.py** 

first = raw_input("enter first number: ")
second = raw_input("enter second number: ")
print first + second

变成了这样:

**Download MoreBuiltins/input_future.py** 

**from builtins import** input
first = input("enter first number: ")
second = input("enter second number: ")
print first + second

当我们使用从 builtin 模块导入的 input()方法时,我们在 builtin input()方法中得到 Python 3 的行为。

对输入使用 eval()来实现 Python 2 的行为,其中输入被评估为其各自的类型。

**Download MoreBuiltins/input_eval_future.py** 

**from builtins import** input
first = eval(input("enter first number: "))
second = eval(input("enter second number: "))
print first + second
注意

eval()使用不慎有其自身的危险性。对不信任的字符串使用 eval()是不安全的。

解析给定类型的用户输入的一种安全方式是将结果转换为所需的类型;例如:

**Download MoreBuiltins/input_cast_future.py** 
**from builtins import** input

first = int(input("enter first number: "))
second = int(input("enter second number: "))
print first + second

使用六

我们还可以使用 six.moves.input(),它在 Python 2 中指向 raw_input(),在 Python 3 中指向 input()。

**Download MoreBuiltins/input_future.py** 

**from six.moves import** input
first = input("enter first number: ")
second = input("enter second number: ")
print first + second

总之,为了在解析用户输入时的兼容性

  • 使用 Python future 的 builtins.input()函数

  • 可以选择使用 six.moves.input()函数

执行()

在 Python 中,exec()执行动态创建的 Python 代码。exec()接受一个字符串或对象。字符串被解析为一组 Python 语句,然后执行这些语句。一个对象被简单地执行。可以使用内置的 globals()和 locals()函数分别返回当前的全局和局部字典;这可能有助于作为 exec()的第二个和第三个参数传递。

在 Python 2 中,exec 是一个语句。

**Download MoreBuiltins/exec_py2.py** 

globalsParameter = {'__builtins__' : None}
localsParameter = {'print': print, 'dir': dir}
exec 'print(dir())' in globalsParameter, localsParameter

在这段代码中,当需要指定全局和局部范围时,exec 是与关键字一起使用的语句。

然而,在 Python 3 中,exec 是一个函数,调用时将对象、全局范围和局部范围作为可选参数。

**Download MoreBuiltins/exec_py3.py** 

globalsParameter = {'__builtins__' : None}
localsParameter = {'print': print, 'dir': dir}
exec ('print(dir())', globalsParameter, localsParameter)

为了兼容,最好的选择是使用 Python 3 语法,因为它在 Python 2 中也能工作。

另一个选择是使用 six.exec_()。

**Download MoreBuiltins/exec_six.py** 

from math import *

globalsParameter = {'__builtins__' : None}
localsParameter = {'print': print, 'dir': dir}
six.exec_('print(dir())', globalsParameter, localsParameter)

这在全局参数局部参数的范围内执行 print(dir())。在这种情况下,print(dir())是一个字符串。如果没有给出全局变量局部变量,它们将默认为调用者的范围。如果只给了全局变量,那么它也被用作局部变量

注意

在 Python 3.x 中,不应使用关键字参数调用 exec(),因为它不接受关键字参数。

总之,在为 exec 提供兼容性时

  • 使用 Python 3 语法

  • 使用 six.exec_()

execfile()

在 Python 2 中,execfile()是一个类似于 exec()的函数,但是它不是接受一个字符串,而是接受一个文件和两个可选的字典作为全局和本地名称空间。

**Download MoreBuiltins/execfile_py2.py** 

import sys

sys.argv = ['arg1', 'arg2']
execfile('abc.py')

Python 3 中删除了这个函数;因此,如果您现有的 Python 2 代码使用这个函数,那么就需要进行修改。让我们来讨论如何做出这些改变。

使用 Python-未来

在 past.builtins 模块中,我们可以使用 execfile()方法,它将在两个 Python 版本中发挥神奇的作用。

**Download MoreBuiltins/execfile_future.py** 

from past.builtins import execfile
import sys

sys.argv = ['arg1', 'arg2']
**execfile('abc.py')** 

我们还可以选择使用 exec()函数和 compile 来以这种方式处理文件:

**Download MoreBuiltins/execfile_exec_compile.py** 

from past.builtins import execfile
import sys

sys.argv = ['arg1', 'arg2']
**execfile(compile(open('abc.py').read()))** 
注意

目前,six 还没有 execfile()的包装器方法,但是有一个公开的问题需要这个特性。当有人打开一个解决这个问题的拉请求时,你可以使用 six 来解决这个兼容性问题;但目前,六号无解。

总之,如果您在代码中使用 execfile(),那么为了兼容性

  • 使用来自 past.builtins 模块的 Python-future 的 execfile()

  • 将 exec()与 compile()一起使用

Unichr()

在 Python 2 中,Unichr()返回一个字符的字符串表示,该字符的 Unicode 码位作为参数给出;例如,

unichr(97)

返回

'a'

该参数的取值范围是从 1 到 1,114,111。超出此范围的任何内容都将返回 ValueError。

在 Python 3 中,这个方法被移除了。相反,使用 chr()获取字符的字符串表示,给出它的 Unicode 点。

Chr(97)

这也返回

'a'

为了兼容,six 和 future 都有 chr()和 unichr()方法。

使用 Python-未来

Python-future 有一个来自 builtins 模块的 chr()方法,该方法在语义上为两个 Python 版本提供了所需的功能。

**Download MoreBuiltins/unichr_future.py** 

**from builtins import** chr

chr(8364)

使用六

six 有一个 Unicode()方法,它接受一个 Unicode 点并返回它各自的字符串表示。

**Download MoreBuiltins/unichr_six.py** 

from six import unichr

unichr(8364)

总而言之,为了兼容使用

  • Python-future's builtins.chr

  • six.chr()

实习生()

如果多次使用包含相同字符或文本的字符串,则每次都会创建一个新的 string 对象。例如,

>>> x = "I love chocolate"
>>> y = "I love chocolate"
>>> x is y
False
>>>

我们最终得到两个不同的 string 对象,它们具有相同的值,但不指向相同的值。然后它们被丢弃,因为它们不再被使用。

另一方面,如果我们使用被拘留的字符串,intern 函数保存字符串并从被拘留的字符串表中返回它们。

>>> a = intern("I love chocolate")
>>> b = intern("I love chocolate")
>>> a is b
True
>>>

由于字符串 ab 被保留,它们现在持有相同的字符串对象;因此, ab 返回。intern 函数确保我们不会创建两个具有相同值的 string 对象。当我们试图请求创建与现有 string 对象具有相同值的第二个 string 对象时,我们获得了对现有 string 对象的引用。这样,我们节省了内存。相比之下,字符串对象非常有效,因为它们是通过比较两个字符串对象的内存地址而不是它们的内容来实现的。这就是 intern 函数有助于提高性能和优化代码的地方。

注意

互联网字符串在技术上不同于普通字符串,因为它们不允许我们创建两个具有相同值的字符串对象;而字符串为同一字符串创建两个单独的对象。

关于实习字符串,这就足够了,但在我们继续之前,我要提一下 Python 2 中的实习函数是全局的,因此使用如下:

**Download MoreBuiltins/internpy2.py** 

a = intern ("I love Python")
b = intern ("I love Python")
a is b  # returns True

在 Python 3 中,intern 函数不是全局函数,因为它被移到了 sys 模块中。因此,它的用法如下:

**Download MoreBuiltins/internpy2.py** 

from sys import intern

a = intern ("I love Python")
b = intern ("I love Python")
a is b  # returns True

这些对比可以通过使用可选导入来处理,这里我们使用一个 try catch 块,如下所示:

**Download MoreBuiltins/intern_optional_import.py** 

try:
    from sys import intern
except ImportError:
    pass

a = intern ("I love Python")
b = intern ("I love Python")
a is b  # returns True

在 Python 2 中,import 语句在执行 except 块时失败,在这里我们实际上什么也不做。这将执行全局实习生功能。在 Python 3 中,执行 try 块中的导入,而使用 sys 模块中的 intern 函数。这只是一种方式。Python-future 和 six 也解决了这个问题。

使用 Python-未来

future 确保 intern 函数兼容性的一种方法是利用它的 past.builtins 模块中的 intern 函数。

**Download MoreBuiltins/intern_future_pastbuiltins.py** 

from past. import intern

a = intern ("I love Python")
b = intern ("I love Python")
a is b  # returns True

future 为内部字符串提供兼容性的另一种方式是通过它的 install_aliases 函数。

**Download MoreBuiltins/intern_future_install_aliases.py** 

install_aliases()

from sys import intern

a = intern ("I love Python")
b = intern ("I love Python")
a is b  # returns True

当我们调用 install_aliases 时,未来的包使 Python 3 sys.intern 函数在 Python 2.x 中可用。

使用六

six 提供了 six.moves.intern 函数来解决 Python 2 和 3 之间的函数间差异。

**Download MoreBuiltins/intern_six.py** 

from six.moves import intern

a = intern ("I love Python")
b = intern ("I love Python")
a is b  # returns True

使用的 intern 函数相当于 Python 2 中的全局 intern 函数和 Python 3 中的 sys.intern 函数。

总之,为了保持内部字符串的兼容性

  • 导入 six.moves 并使用 intern 函数。这相当于 Python 2 中的 intern 和 Python 3 中的 sys.intern。

  • 调用 future 的 install_aliases 使 Python 3 的 sys.intern 函数在 Python 2.x 中可用

  • 导入 past.builtins 并使用 intern 函数。

应用

apply 是一个 Python 2 函数,它基本上执行一个函数。它将函数和函数参数列表作为参数。

**Download MoreBuiltins/apply_py2.py** 

def add(x, y):
     return x + y

apply (add, [1,2])

在 Python 中,这个函数已经过时了;现在,我们必须以正常方式调用该函数,如下所示:

**Download MoreBuiltins/apply_py3.py** 

def add(x, y):
     return x + y

add (1,2)

因此,如果我们有使用 apply 方法的 Python 2 代码,那么我们应该将其更改为以正常方式调用函数,其中我们调用方法及其参数,而不是使用 apply 来执行函数以支持 Python 3。这节省了时间,并允许更快地执行我们的程序。如果您仍然想使用 apply 方法,那么使用 Python-future。

注意

在 Python 3 中,apply 方法已经过时。

使用 Python-未来

当我们从 future 的 past.builtins 模块调用 apply 函数时,我们可以继续使用 apply 函数,并且仍然支持 Python 3。

**Download MoreBuiltins/apply_future.py** 

from past.builtins import apply

def add(x, y):
     return x + y

apply (add, [1,2])

这使用了 Python 3 中的 apply 函数。

总之,为了在 Python 2 代码中使用 apply 函数时支持 Python 3

  • 更改您的代码以正常调用有问题的方法,而不是通过 apply 函数执行它。

  • 从 past.builtins 模块调用 future 的 apply 方法。

chr()

chr()通常接受一个整数,即字符的 Unicode 点,并返回一个或多个字符串。

在 Python 2.x 中,chr()将 0–255 范围内的数字转换为字节字符串;一个字符具有数值和 Unicode(),后者将 0–0x 10 ffff 范围内的数字转换为具有该 Unicode 码位的一个字符的 Unicode 字符串。

**Download MoreBuiltins/chr_py2.py** 

chr(64)
chr(200)

如前所述,Python 3.x 用 chr()替换了 unichr()。因此,要实现旧的 chr()功能,我们有两个选择。一种是对 chr()的结果进行编码。

**Download MoreBuiltins/chr_py3_1.py** 

chr(64).encode('latin-1')
chr(0xc8).encode('latin-1')

另一个选择是用我们想要得到的字符串的 Unicode 点来调用全局函数 bytes。

**Download MoreBuiltins/chr_py3_2.py** 

bytes([64])
bytes([0xc8])

为了兼容性,six 和 future 都提供了避开这些语义差异的方法。让我们逐一探究。

使用 Python-未来

future 提供的一个选项是来自内置模块的 chr(),它使用与调用 chr()和编码结果相同的 Python 3 语法。

**Download MoreBuiltins/chr_future_1.py** 

from builtins import chr

chr(64).encode('latin-1')
chr(0xc8).encode('latin-1')

或者,我们可以使用 future 内置模块中的 bytes 方法,它使用与 Python 3 的全局函数 bytes 相同的语法。

**Download MoreBuiltins/chr_future_2.py** 

from builtins import bytes

bytes([64])
bytes([0xc8])

使用六

six 提供了 int2byte(i)函数,该函数接受(0,256)范围内的整数,并将其转换或返回为字节。

**Download MoreBuiltins/chr_six.py** 

from six import int2byte

int2byte(64)
int2byte(0xc8)

总之,

  • chr()在 Python 2 中返回字节字符串,而在 Python 3 中它必须被编码。

  • 使用 Python-future 的 bytes 或 chr()方法获取字节字符串。

  • 我们还可以使用 six 的 Int2byte()方法来获取字节串。

化学机械抛光()

cmp()是一个比较值的 Python 2 函数;以两个值为例,a 和 b。

cmp(a, b)

结果是:

  • 如果 a 小于 b,则为负数。

  • 如果 a 等于 b,则为零。

  • 如果 a 大于 b,则为正数。

Python 3 中不赞成使用该函数。让我们讨论如何为这种方法提供 Python 3 支持。

使用 Python-未来

我们应该使用 future's past.builtins 模块中的 cmp()函数。

**Download MoreBuiltins/cmp_future.py** 

from past.builtins import cmp

cmp('a', 'b') < 0
cmp('b', 'a') > 0
cmp('c', 'c') == 0

这使用了 Python 3 中的 cmp 函数。

重新加载()

reload 函数重新加载先前导入的模块。当您使用外部编辑器编辑了模块源文件,并且想要在不离开 Python 解释器的情况下尝试新版本时,这一点非常重要。返回值是模块对象。

注意

参数应该是已成功导入的模块。

在 Python 2 中,reload()是一个全局函数。

**Download MoreBuiltins/reload_py2.py** 

reload(mymodule)

在 Python 3 中,方法是在 3.4 及更高版本中使用 importlib。

**Download MoreBuiltins/reload_py3.py** 

from importlib import reload

reload(mymodule)

为了中立的兼容性,我们使用 imp.reload。

**Download MoreBuiltins/reload_neutral.py** 

import imp
imp.reload(module)
注意

imp.reload()适用于 Python 版及更低版本。importlib.reload()适用于 Python 版及更高版本。

摘要

本章讨论了实现两个内置函数兼容性的技术,比如 reduce、input 和 cmp 等。这些函数中的大多数都已被弃用、重命名或重组。补救办法是 future 的内置包和 six 的 six.moves 包。

任务:巴格特大师

今天手头的任务是巴格特计划。这个项目包含一个 bagcat.py 文件,其中的方法仍然使用 Python 2 的 raw_ input。

**Download MoreBuiltins/task.py** 
def write_config(args):
    config_file = os.path.join(os.path.expanduser("∼"), '.bagcat')
    if os.path.isfile(config_file):
        if raw_input("overwrite existing %s [Y/N] " % config_file).upper() != "Y":
            return
    config = configparser.RawConfigParser()
    for name in ['aws_access_key_id', 'aws_secret_access_key', 'bucket']:
        value = raw_input("%s: " % name)
        config.set('DEFAULT', name, value)
    config.write(open(config_file, 'w'))

让我们看看这段代码兼容后是什么样子。

使用 Python-未来

如前所述,使用 builtins 包中的 input()。

**Download MoreBuiltins/task_future.py** 
from builtins import input
def write_config(args):
    config_file = os.path.join(os.path.expanduser("∼"), '.bagcat')
    if os.path.isfile(config_file):
        if input("overwrite existing %s [Y/N] " % config_file).upper() != "Y":
            return
    config = configparser.RawConfigParser()
    for name in ['aws_access_key_id', 'aws_secret_access_key', 'bucket']:
        value = input("%s: " % name)
        config.set('DEFAULT', name, value)
    config.write(open(config_file, 'w'))

使用六

与 Python-future 一样,使用 six.moves 中的 input()。

**Download MoreBuiltins/task_six.py** 
from six.moves import input
def write_config(args):
    config_file = os.path.join(os.path.expanduser("∼"), '.bagcat')
    if os.path.isfile(config_file):
        if input("overwrite existing %s [Y/N] " % config_file).upper() != "Y":
            return
    config = configparser.RawConfigParser()
    for name in ['aws_access_key_id', 'aws_secret_access_key', 'bucket']:
        value = input("%s: " % name)
        config.set('DEFAULT', name, value)
    config.write(open(config_file, 'w'))

十二、标准库模块

Python 标准库进行了重组,以使其更加简单和一致。许多模块被重命名以符合 PEP 8 和统一文件命名惯例。其他的合并将相关的模块放在一个公共的名称空间中。本章探讨了一些重新命名和重新组织的模块,并解释了如何为它们提供兼容性。

Tkinter

如果你尝试过使用图形用户界面来创建程序,那么你会遇到 Tkinter。它是一个标准的库模块,作为一个小型工具包 Tk 的接口。

该模块在 Python 2 中被称为 tkinter,但在 Python 3 中被重命名为 Tkinter(区别在于第一个字母的大小写)。

与 GUI 开发相关的其他模块从全局名称空间中移除,放在 tkinter 名称空间中。表 12-1 列出了这些模块。

表 12-1。Tkinter 命名空间模块
|

Python 2

|

Python 3

|
| --- | --- |
| 对话 | tkinter 对话框 |
| 文件动态 | tkinter.filedialog 对话方块 |
| 滚动文本框 | tkinter . scroll ed text-滚动文字 |
| 简单对话 | tkinter . simple dialog-简易对话方块 |
| 置 | tkinter.tix |
| tk 常量 | tkinter 常量 |
| Tkdnd | tkinter.dnd |
| tkColorChooser | 克罗库瑟 |
| tkCommonDialog | tkinter . common dialog-tkinter .共用对话方块 |
| tkFileDialog | tkinter.filedialog 对话方块 |
| tkFont | tkinter 字体 |
| tkMessageBox | tkinter . message box-讯息方块 |
| tkSimpleDialog | tkinter . simple dialog-简易对话方块 |
| 天天快递 | tkinter.ttk |

Python-future 和 six 提供了包装器来保持代码库中使用 Tkinter 模块时的兼容性。以下是带有 Tkinter 导入的 Python 2 源代码:

**Download MoreBuiltins/tkinter_py2.py** 

import Tkinter
import Dialog
import FileDialog
import ScrolledText
import SimpleDialog
import Tix
import Tkconstants
import Tkdnd
import tkColorChooser
import tkCommonDialog
import tkFileDialog
import tkFont
import tkMessageBox
import tkSimpleDialog
import ttk

使用 Python-未来

在 Python-future 中,前面的 Tkinter 模块可以按如下方式实现:

**Download MoreBuiltins/tkinter_future.py** 

import tkinter
import tkinter.dialog
import tkinter.filedialog
import tkinter.scrolledtext
import tkinter.simpledialog
import tkinter.tix
import tkinter.constants
import tkinter.dnd
import tkinter.colorchooser
import tkinter.commondialog
import tkinter.filedialog
import tkinter.font
import tkinter.messagebox
import tkinter.simpledialog
import tkinter.ttk

这些未来的包装器在 Python 2 和 3 中调用正确的 tkinter 模块。

使用六

通过它的 six.moves 模块,six 以一个与 Python 2 和 3 都兼容的名称公开了所有重命名和公开的模块。

**Download MoreBuiltins/tkinter_future.py** 

from six.moves import tkinter

from six.moves  import dialog
from six.moves import filedialog
from six.moves import scrolledtext
from six.moves import simpledialog
from six.moves import tix
from six.moves import constants
from six.moves import dnd
from six.moves import colorchooser
from six.moves import commondialog
from six.moves import filedialog
from six.moves import font
from six.moves import messagebox
from six.moves import simpledialog
from six.moves import .ttk

推荐的固定器将导入更改为使用六步。

配置器

Configparser 是一个使用配置文件的 Python 模块。它与 Windows INI 文件有许多相似之处。它为任何应用处理用户可编辑的配置文件。配置文件分为几个部分,每个部分可以包含配置数据的名称-值对。通过查找以[和]开头的行来识别配置文件节。典型的配置文件如下所示:

**Download StandardLibraryModules/sample_config.py** 
[*AWS_main*]
endpoint = https://www.amazon.com/
accesskey = mdl;ldklsld
secretkey = fkndjfdgkdfhsdfj

在 Python 2 中,这个模块被称为 Configparser(首字母大写;因此这是它的使用方式)。

**Download StandardLibraryModules/configparser_py2.py** 

*from ConfigParser import ConfigParser* 

ConfigParser.SafeConfigParser()

此模块已被重命名。

**Download StandardLibraryModules/configparser_py2.py** 

*from* configparser *import ConfigParser* 

configparser.ConfigParser()

从这个例子中,我们看到 SafeConfigParser 函数在 Python 3 中被重命名为 ConfigParser。

要解决这些差异并保持兼容性,请使用 six.moves 或 configparser backport。

使用 configparser 反向端口

configparser backport 是使用 pip install configparser 安装的 PyPi 可安装程序包。安装这个包,将它导入到您的代码中,并使用 Python 3 重命名的模块在两个 Python 版本上都有效。

**Download StandardLibraryModules/configparser_backport.py** 

*from* configparser. *import ConfigParser* 

configparser.ConfigParser()

用六招

从 six.moves 模块中,导入 configparser,它适用于 Python 2 和 3。

**Download StandardLibraryModules/configparser_six.py** 

*import six* 

*from six.moves import configparser* 

*configparser.ConfigParser()* 

总之,为了读写配置文件时的兼容性:

  • 安装并使用后端口 configparser

  • 使用 six.moves.configparser

长队

队列模块实现队列来帮助多线程编程。Python 队列对象可用于解决多生产者、多消费者的问题,其中消息必须在多个线程之间安全地交换。锁定语义已经在队列类中实现;因此,不需要处理低级锁定和解锁操作,这可能会导致死锁问题。

在 Python 3 中队列模块被重命名为“Queue”;因此,如果您想在 Python 2 中使用这个模块:

**Download StandardLibraryModules/queue_py2.py** 

from Queue import  Queue

在 Python 3 中应该是这样的:

**Download StandardLibraryModules/queue_py3.py** 

from queue import  Queue

通过使用 six 和 Python-future,我们可以导入与 Python 2 和 3 都兼容的同步队列类。

使用 Python-未来

在 Python-future 中,我们导入了一个在两个 Python 版本中都可靠运行的队列类。

**Download StandardLibraryModules/queue_future.py** 

from queue import  Queue

使用六

像 Python-future 一样,six 具有单个队列类,我们可以用它来确保 Python 2 和 3 的兼容性。

**Download StandardLibraryModules/queue_six.py** 

import six
from six.moves.queue import  Queue, heapq, deque

总之,为了兼容性,使用

  • 来自 Python 的队列-未来

  • 六.移动.排队

套接字服务器

socketserver 模块是一个简化网络服务器创建的框架。它提供了处理 TCP、UDP、Unix 流和 Unix 数据报上的同步网络请求的类。根据具体情况,它还提供了一些类来转换服务器,以便为每个请求使用单独的线程或进程。

socketserver 模块中定义了五个不同的服务器类;BaseServer 定义了 API。即使直接使用,这个类也不应该被实例化。TCPServer 使用 TCP/IP 套接字进行通信。UDPServer 使用数据报套接字。UnixStreamServer 和 UnixDatagramServer 使用 Unix 域套接字。

在 Python 2 中,socketserver 模块被拼写为“socket server”——使用骆驼大写字母。

**Download StandardLibraryModules/socketserver_py2.py** 

import SocketServer

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

在 Python 3 中,这个模块被更改并重命名为“socket server”——全是小写字母。

**Download StandardLibraryModules/socketserver_py2.**                                      **py** 

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

为了兼容 Python 2 和 3,Python-future 提供了 socketserver 模块,six 提供了 moves.socketserver 模块来解决这些差异。

使用 Python-未来

如果您 pip 安装了 Python-future,那么您应该可以访问 Python-future 的 socketsever 模块,您可以导入和使用该模块。

**Download StandardLibraryModules/socketserver_future.py** 

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

就像导入的模块名称是 Python 3 的名称一样,如果不安装 future,那么这段代码将无法在 Python 2 上运行。

使用六

在 six.moves 中,我们可以导入和使用 socketserver 模块。

**Download StandardLibraryModules/socketserver_six.py** 

from six.moves import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

总之,为了在使用 socketserver 模块时保持兼容性,导入

  • 来自 Python-future 的 socketserver

  • socketserver from six.moves

dbm 模块

Python 有一个数据库 API,在处理不同类型的数据库时非常有用。这些数据存储在一个名为 DBM 的数据库管理器中。从本质上讲,持久性字典的功能与普通的 Python 字典相同,只是数据是从磁盘上读写的。

有许多 DBM 模块不兼容。在 Python 2 中,其中一些模块的用法如下:

**Download StandardLibraryModules/dbmmodule_py2.py** 

import anydbm
import whichdb
import dbm
import dumbdbm
import gdbm

在 Python 3 中,这些模块被重组并转移到子模块 DBM 中。

**Download StandardLibraryModules/dbmmodule_py3.py** 

import dbm.anydbm
import dbm.whichdb
import dbm.dbm
import dbm.dumbdbm
import dbm.gdbm

不同之处在于子模块,模块被移到子模块中,因为它们在 Python 2 中不存在。让我们看看如何使用 Python-future 和 six 来平衡源代码。

使用 Python-未来

使用 Python-future,我们可以导入 standard_library 模块,调用 install_aliases()方法,然后使用 Python 3 语法。

**Download StandardLibraryModules/dbmmodule_future1.py** 

from future import standard_library
standard_library.install_aliases()

import dbm
import dbm.ndbm
import dbm.dumb
import dbm.gnu

或者,我们可以从 future.moves 导入这些模块

**Download StandardLibraryModules/dbmmodule_future2.py** 

from future.moves import dbm
from future.moves.dbm import dumb
from future.moves.dbm import ndbm
from future.moves.dbm import gnu

使用六

六提供了 DBM 的 gnu 模块;但是在写这本书的时候,它没有提供对其他不兼容模块的支持。

**Download StandardLibraryModules/dbmmodule_six.py** 

from six.moves import dbm_gnu

http 模块

这个模块定义了处理 HTTP 和 HTTPS 协议客户端的类。大多数处理 HTTP 的模块都被移到了包 HTTP。

在 Python 2 中,HTTP 模块被全局访问使用,如下所示:

**Download StandardLibraryModules/http_py2.py** 

import httplib
import Cookie
import cookielib
import BaseHTTPServer
import SimpleHTTPServer
import CGIHttpServer

在 Python 3 中,这些模块被重组;BaseHTTPServer 等模块在 http.server 中。

**Download StandardLibraryModules/http_py3.py** 

import http.client
import http.cookies
import http.cookiejar
import http.server

为了兼容性,我们可以使用 Python-future 或 six 来实现平衡。

使用 Python-未来

在 pip 安装 future 之后,相应的 Python 3 语法应该可以在两个 Python 版本中可靠地工作。

Download StandardLibraryModules/http_future.py

import http.client
import http.cookies
import http.cookiejar
import http.server

使用六

在 six.moves 包中,我们也可以导入这些模块,让它们在 Python 2 和 3 中都能很好地工作。

Download StandardLibraryModules/http_future.py

from six.moves import http.client
from six.moves  import http.cookies
from six.moves  import http.cookiejar
from six.moves import http.server

XML-RPC

XML-RPC 是一个远程过程调用方法,它使用通过 HTTP 传递的 XML 作为传输。 1 因此,客户端可以在远程服务器上调用带参数的方法来获取结构化数据。

在 Python 2 中,有两个模块——DocXMLRPCServer 和 SimpleXMLRPCServer——和 xmlrpclib 来处理服务器和客户端 XML-RPC。

Download StandardLibraryModules/xmlrpc_py2.py

# for servers
import DocXMLRPCServer
import SimpleXMLRPCServer

# for clients
import xmlrpclib

前面的模块都组织在一个包 xmlrpc 中,该包包含两个模块,分别为客户机和服务器处理 xmlrpc。

**Download StandardLibraryModules/xmlrpc_py3.py** 

import xmlrpc.client
import xmlrpc.server

在 pip 安装 future 之后,我们可以使用 Python 3 语法,它在 Python 2 和 Python 3 中都可以可靠地工作。

**Download StandardLibraryModules/xmlrpc_future.py** 

import xmlrpc.client
import xmlrpc server

ifilterfalse

根据定义,ifilterfalse 返回那些 function(item)为 false 的序列项。如果函数为 None,则返回 false 项。在 Python 3 中,这个函数被重命名为 filterfalse。为了保持兼容性,我们将使用 Python-future 或 six,如下所示。

使用六

导入并使用重命名的值;也就是说,使用 filterfalse 而不是 ifilterfalse。

**Download StandardLibraryModules/filterfalse_six.py** 

from six.moves import filterfalse

list=[1,2,3,4,5]
list(itertools.filterfalse(filterfalse, list))

使用未来

与 six 一样,从 future.moves.itertools 导入并使用 filterfalse。

**Download StandardLibraryModules/filterfalse_six.py** 

from future.moves.itertools import filterfalse
list=[1,2,3,4,5]
list(itertools.filterfalse(filterfalse, list)

izip_longest

在 Python 3 中 izip_longest 被重命名为 zip _ longest 因此,为了兼容,请使用六个或更多选项。

使用六

导入并使用重命名的值;也就是用 zpl_longest 代替 iziplongest。

**Download StandardLibraryModules/filterfalse_six.py** 

from six.moves import zip_longest

def solve(seq):    
        sentinel = object()    
        return [tuple(x for x in item if x is not sentinel) for item zip_longest(*seq, fillvalue=sentinel)]

使用未来

与 six 一样,从 future.moves.itertools 导入并使用 zip_longest。

**Download StandardLibraryModules/filterfalse_six.py** 

from future.moves.itertools import zip+longest
def solve(seq):    
        sentinel = object()    
        return [tuple(x for x in item if x is not sentinel) for item zip_longest(*seq, fillvalue=sentinel)]

用户字典、用户列表和用户字符串

如果我们想在 Python 2 中导入这些模块,那么我们从单独的包中导入它们。

**Download StandardLibraryModules/user_py2.py** 

from UserDict import UserDict
from UserList import UserList
from UserString import UserString

在 Python 3 中,它们都被重组为一个公共包,称为集合;因此,我们按如下方式导入它们:

**Download StandardLibraryModules/user_py3.py** 

from collections import UserDict, UserList, UserString

For compatibility, we will use future or six.

使用六

从通用的 six.moves 包中导入它们。

from six.moves import UserDict, UserList, UserString

使用未来

从 future.moves.collections 包中导入它们。

from future.moves.collections import UserDict, UserList, UserString

Copy_reg

Python 2 中的 copy_reg 模块提供了一个注册表来注册我们自己的扩展类型。

**Download StandardLibraryModules/copy_reg_py2.py** 

import copy_reg

def test_class(self):       
         self.assertRaises(TypeError, copy_reg.pickle, C, None, None)

这个模块在 Python 3 中被重命名为 copyreg。

**Download StandardLibraryModules/copy_reg_py.py** 

import copyreg

def test_class(self):       
         self.assertRaises(TypeError, copyreg.pickle, C, None, None)

为了兼容,使用 future 或 six,如下所示。

使用六

从 six.moves 导入模块。

**Download StandardLibraryModules/copy_reg_six.py** 

From six.moves import copyreg

def test_class(self):       
         self.assertRaises(TypeError, copyreg.pickle, C, None, None)

使用未来

pip 安装 future 后导入 copyreg,在 Python 2 和 3 中都能可靠工作。

**Download StandardLibraryModules/copy_reg_future.py** 

import copyreg

def test_class(self):       
         self.assertRaises(TypeError, copyreg.pickle, C, None, None)

摘要

本章讨论了实现 socketserver、zip_longest 等标准库函数兼容性的技术。大多数函数都被弃用、重命名或重新组织。Python-future 的内置包和 six 的 six.moves 包给出了补救办法。

任务:Python-Jenkins

今天,你的任务来自一个叫做 Python-jenkins 的项目。该项目位于具有不兼容的 init 方法的 helper.py 文件中。

**Download StandardLibraryModules/task.py** 

def __init__(self, server_address, *args, **kwargs):
    # TCPServer is old style in python 2.x so cannot use
    # super() correctly, explicitly call __init__.

    # simply init'ing is sufficient to open the port, which
    # with the server not started creates a black hole server
    socketserver.TCPServer.__init__(
        self, server_address, socketserver.BaseRequestHandler,
        *args, **kwargs)

经过一些编辑,这段代码的兼容版本如下所示。

使用六

使用 six.moves 中的 socketserver 模块。

**Download StandardLibraryModules/task_six.py** 

from six.moves import socketserver

def __init__(self, server_address, *args, **kwargs):
    # TCPServer is old style in python 2.x so cannot use
    # super() correctly, explicitly call __init__.

    # simply init'ing is sufficient to open the port, which
    # with the server not started creates a black hole server
    socketserver.TCPServer.__init__(
        self, server_address, socketserver.BaseRequestHandler,
        *args, **kwargs)

使用未来

将来使用 socketserver 模块。

**Download StandardLibraryModules/task_future.py** 

 import socketserver

def __init__(self, server_address, *args, **kwargs):
    # TCPServer is old style in python 2.x so cannot use
    # super() correctly, explicitly call __init__.

    # simply init'ing is sufficient to open the port, which
    # with the server not started creates a black hole server
    socketserver.TCPServer.__init__(
        self, server_address, socketserver.BaseRequestHandler,
        *args, **kwargs)
posted @ 2024-08-10 15:27  绝不原创的飞龙  阅读(11)  评论(0编辑  收藏  举报