2

我知道我已经很晚了,但是我有一个相对简单的命令行 Python 脚本,它是为 2.7 编写的,我想让它在 Python 2.7+ 和 Python 3+ 上都可用。因为它是一个脚本:

  • 我不想使用六个- 虽然six只是一个文件,但现在我必须处理两个文件(six模块和我的脚本),而不是一个
  • 我不想使用2to3;因为话又说回来,我必须处理两个文件(我的脚本的 2.7 版本和它的 3.2 版本),而不是一个

所以,我认为对我来说最好的方法是编写尽可能与 Python 3.x 兼容的 Python 2.x;然后我可以编码一次,不用担心如果我必须在 USB 拇指驱动器操作系统上运行脚本,它可能只有Python 2.7(或者就此而言,只有Python 3+),我可能很难找到和/或安装正确版本的 Python。

为了演示我的问题,这里有一个示例脚本,它基于Learning Python 中的示例——示例第 9 章:Python 中的常见任务——以及在 Ubuntu 11.04 上的准备工作bash(带有一点 Unicode,以增加趣味):

cd /tmp

mkdir /tmp/ptest
echo 'Байхъусут, зæрæдтæ!.. Байхъусут, лæппутæ!..' > /tmp/ptest/test.txt
echo 'Байхъусут, зæрæдтæ!.. Байхъусут, лæппутæ!..
Байхъусут зарæгмæ, фыдæлты кадæгмæ,
Дзæбæхдæр бахъырнут уæ бæзджын хъæлæстæй!..' > /tmp/ptest/Байхъусут.txt

cat > tscript.py <<"EOF"
# -*- coding: utf-8 -*-
import fileinput, sys, string, os

if ( len(sys.argv) > 3 ) or ( len(sys.argv) < 2 ):
  print "Usage: ", sys.argv[0], "searchterm [path]"
  sys.exit()

# take the first argument out of sys.argv and assign it to searchterm
searchterm, sys.argv[1:] = sys.argv[1], sys.argv[2:]

if len(sys.argv) == 1:                  # if no dir is specified,
  indir = os.curdir                     #   use current dir
else:                                   # otherwise, use dir specified
  indir = sys.argv[1]                   #   on the command line

filenames = [indir+"/"+f for f in os.listdir(indir) if os.path.isfile(indir+"/"+f)]

for line in fileinput.input(filenames):
  num_matches = string.count(line, searchterm)
  if num_matches:                     # a nonzero count means there was a match
    print "found '%s' %d times in %s on line " % ( searchterm, num_matches, fileinput.filename() ), \
      fileinput.filelineno()
EOF

试试这个:

$ python2.7 tscript.py Байхъусут /tmp/ptest
found 'Байхъусут' 2 times in /tmp/ptest/test.txt on line  1
found 'Байхъусут' 2 times in /tmp/ptest/Байхъусут.txt on line  1
found 'Байхъусут' 1 times in /tmp/ptest/Байхъусут.txt on line  2

$ python3.2 tscript.py Байхъусут /tmp/ptest
  File "tscript.py", line 17
    print "Usage: ", sys.argv[0], "searchterm [path]"
                  ^
SyntaxError: invalid syntax

好的,那一定是打印的变化 - 只是添加括号吗?我这样改变:

  print ("Usage: ", sys.argv[0], "searchterm [path]")
  ....
    print ("found '%s' %d times in %s on line " % ( searchterm, num_matches, fileinput.filename() ), \
      fileinput.filelineno() )

......会这样做吗?:

$ python3.2 tscript.py Байхъусут /tmp/ptest
Traceback (most recent call last):
  File "tscript.py", line 31, in <module>
    num_matches = string.count(line, searchterm)
AttributeError: 'module' object has no attribute 'count'

不..所以我也改变了这一行:

  num_matches = line.count(searchterm) # string.count(line, searchterm)

... 够了吗?嗯 - 有点,似乎:

$ python3.2 tscript.py Байхъусут /tmp/ptest
found 'Байхъусут' 2 times in /tmp/ptest/test.txt on line  1
found 'Байхъусут' 2 times in /tmp/ptest/Байхъусут.txt on line  1
found 'Байхъусут' 1 times in /tmp/ptest/Байхъусут.txt on line  2
$ python2.7 tscript.py Байхъусут /tmp/ptest
("found '\xd0\x91\xd0\xb0\xd0\xb9\xd1\x85\xd1\x8a\xd1\x83\xd1\x81\xd1\x83\xd1\x82' 2 times in /tmp/ptest/test.txt on line ", 1)
("found '\xd0\x91\xd0\xb0\xd0\xb9\xd1\x85\xd1\x8a\xd1\x83\xd1\x81\xd1\x83\xd1\x82' 2 times in /tmp/ptest/\xd0\x91\xd0\xb0\xd0\xb9\xd1\x85\xd1\x8a\xd1\x83\xd1\x81\xd1\x83\xd1\x82.txt on line ", 1)
("found '\xd0\x91\xd0\xb0\xd0\xb9\xd1\x85\xd1\x8a\xd1\x83\xd1\x81\xd1\x83\xd1\x82' 1 times in /tmp/ptest/\xd0\x91\xd0\xb0\xd0\xb9\xd1\x85\xd1\x8a\xd1\x83\xd1\x81\xd1\x83\xd1\x82.txt on line ", 2)

现在至少它不会崩溃 - 但是python 2.7会print看到一个元组,显然它默认不会解码该元组内的字符串......

所以,显然,现在我想为 python 2.7 导入(哪个 python 版本需要print_function__future__ 导入 with_statement?);所以我尝试把它放在文件的顶部(在语句之后),认为我最好尝试只对 2.x 版本使用导入:__future__coding

import __future__, sys
if sys.version_info[0] < 3:
  from __future__ import print_function
else:
  pass

...但我得到:

$ python2.7 tscript.py Байхъусут /tmp/ptest
  File "tscript.py", line 6
    from __future__ import print_function
SyntaxError: from __future__ imports must occur at the beginning of the file

在Python 优雅的未来功能 (__future__) 导入问题中,对此的答案是使用包装.py文件 - 但是,我又遇到了同样的问题,不得不考虑两个文件,而不是一个。

我以为我可以这样作弊——即使它确实创建了一个额外的文件:

import __future__, sys
if sys.version_info[0] < 3:
  str = """from __future__ import print_function"""
  f = open('compat23.py','w')
  f.write(str)
  f.close()
  import compat23
  print("sys.version_info[0] < 3", end='(')
else:
  print("sys.version_info[0] >= 3", end=')')

...但这并不重要:

$ python2.7 tscript.py Байхъусут /tmp/ptest
  File "tscript.py", line 11
    print("sys.version_info[0] < 3", end='(')
                                        ^
SyntaxError: invalid syntax

...因为__future__导入显然仅对新创建的compat23模块的范围有效。

所以:

  • __future__鉴于这from __future__ ...是一个编译时语句,我试图将导入仅限于低于 3 的版本显然是错误的;但是之后:
  • Python 3 对这种说法有何反应?它会被忽略吗?
  • 那么,当在 Python 4 中他们决定再次弃用时会发生什么——即使它目前在 Python 3 中可能被忽略,在 Python 3 中print也不会再次具有意义?from __future__ import print_function

因此,我想,如果我想避免考虑这一点,并且仍然使用仅单个文件的脚本,那么我将遵循 noconv.html中的建议:“ ...或者您可以使用单独的打印功能在 Python 2 和 Python 3 下 .. 诀窍是使用 sys.stdout.write() 和格式化 .... "; 也可以在Eli Bendersky 的网站上看到 » 使代码与 Python 2 和 3 兼容

所以我在文件的开头尝试这个,而不是__future__导入部分 - 并更改相应的打印语句:

def printso(*inargs):
  outstr = ""
  for inarg in inargs:
    outstr += str(inarg) + " "
  outstr += "\n"
  sys.stdout.write(outstr)

.... printso ("Usage: ", sys.argv[0], "searchterm [path]") .... printso ("在 %s 行中找到 '%s' %d 次" % ( searchterm, num_matches, fileinput.filename()), \fileinput.filelineno())

...确实,这在 python 2.7 和 3.2 中都可以正常工作:

$ python2.7 tscript.py Байхъусут /tmp/ptest
found 'Байхъусут' 2 times in /tmp/ptest/test.txt on line  1
found 'Байхъусут' 2 times in /tmp/ptest/Байхъусут.txt on line  1
found 'Байхъусут' 1 times in /tmp/ptest/Байхъусут.txt on line  2
$ python3.2 tscript.py Байхъусут /tmp/ptest
found 'Байхъусут' 2 times in /tmp/ptest/test.txt on line  1
found 'Байхъусут' 2 times in /tmp/ptest/Байхъусут.txt on line  1
found 'Байхъусут' 1 times in /tmp/ptest/Байхъусут.txt on line  2

%好的,但现在事实证明,字符串格式的百分号也已被弃用;所以我应该写:

  #printso ("found '%s' %d times in %s on line " % ( searchterm, num_matches, fileinput.filename() ), \
  #  fileinput.filelineno() )
  printso ("found '{0}' {1} times in {2} on line ".format(searchterm, num_matches, fileinput.filename() ), \
    fileinput.filelineno() )

值得庆幸的是,这适用于 2.7 和 3.2,以及新的 Python 3.0 字符串格式 - 真的有必要吗?-comp.lang.python | 谷歌群组声明:

>> 你也可以在 Python 3.x 中使用旧的 2.x 语法:

> 是的,但它已被弃用,而且——据我所知——可能会
在未来的版本中完全删除。此外,将来,如果您正在
使用 > 来自其他开发人员的代码,那么开发人员很可能会使用
> 新格式。我想你可以同时使用两者——但那会是多么糟糕的一团糟

它不会被移除很多年——如果有的话。

...但是,鉴于它已被弃用,谁能确定这将保持多久?

所以,基本上 - 我想确认:

  • from __future__ import在 Python 3 中表现如何?当 Python 4 出现时,当时的 Python 3 包含已弃用的功能,必须从“未来”的 Python 4 中导入?
  • 对于这个角色的脚本,我想将其保存在单个.py文件中,并且兼容 Python 2.7 和(希望)3+:我最好print基于 编写自己的函数sys.stdout.write,并在任何地方使用它,而不是搞乱__future__
  • 我是否也最好在任何地方使用新的字符串格式化语法?
4

1 回答 1

2

Python 的__from__ future import feature语句是向前兼容的。也就是说,即使feature在将来的版本中成为标准,导入声明仍然是合法的。

因此,与其做一堆工作来让你自己的print函数工作,不如把它无条件地放在你文件的顶部(在任何其他代码之前):

from __future__ import print_function

它只会工作,永远。

于 2013-04-27T01:31:44.797 回答