Python 2 和 3 兼容性指南(一)
一、打印、反引号和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(文本)字符串进行了非常明显的区分。它带有三种字符串对象类型: str 、 bytes 和 bytearray 。 str 类型代表 unicode 文本,它可以被任何需要处理未按任何文本格式编码的原始二进制数据的程序使用,例如图像文件和打包数据。相比之下,字节类型表示二进制数据,基本上是 0–255 范围内的小整数序列,为了方便起见,它们被打印为字符串而不是整数。 bytearray 类型是 bytes 类型的可变变体,支持 str 和 bytes 所做的常见字符串操作,但也有许多与列表相同的就地更改操作。
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 中我们有两种类型的字符串类型 str 和 *unicode。*Python 2 中的这些字符串类型处于一个层次结构中,它们是类型 basestring 的后代。
类型 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 改变了这种层次结构;相反,它将类型 str 和 byte 作为基本类型对象的后代。
工具 2to3 用 str 替换 basestring ,因为在 Python 3 中 str 代表 Python 2 的 str 和 unicode 类型。
在 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