Python 与 Perl 中的小写脚本

Lowercasing script in Python vs Perl

在 Perl 中,要将文本文件小写,我可以执行以下操作 lowercase.perl:

#!/usr/bin/env perl

use warnings;
use strict;

binmode(STDIN, ":utf8");
binmode(STDOUT, ":utf8");

while(<STDIN>) {
  print lc($_);
}

并且在命令行上:perl lowercase.perl < infile.txt > lowered.txt

Python 中,我可以用 lowercase.py:

#!/usr/bin/env python
import io
import sys

with io.open(sys.argv[1], 'r', 'utf8') as fin:
    with io.open(sys.argv[2], 'r', 'utf8') as fout:
        fout.write(fin.read().lower())

并且在命令行上:python lowercase.py infile.txt lowered.txt

Perl lowercase.perl 与 Python lowercase.py 不同吗?

它是否流式传输输入并在输出时将其小写?还是像 Python 的 lowercase.py?

一样读取整个文件

有没有一种方法可以将输入流式传输到 Python 并逐字节或逐字符输出小写字母,而不是读取整个文件?

有没有办法控制命令行语法,使其遵循 Perl STDIN 和 STDOUT?例如。 python lowercase.py < infile.txt > lowered.txt?

Perl lowercase.perl 与 Python lowercase.py 不同吗?

Python 文件采用文件名作为输入和输出。 Perl 文件进行流式处理(例如可以在 some_command | your_perl_script.pl | some_other command 中使用)。

它是否流式传输输入并在输出时将其小写?还是像 Python 的 lowercase.py?

一样读取整个文件
while(<STDIN>) {

逐行浏览您的输入。只要您的输入包含 \n(默认换行符,可以通过 setting $/ 更改)。这是直播。

有没有一种方法可以将输入流式传输到 Python 并逐字节或逐字符输出小写字母,而不是读取整个文件?

可能是,但我不知道 Python :(

在示例中,唯一的区别是访问数据的方式。一种是通过打开文件(python 版本),另一种是通过 piping i/o 到程序(perl 版本)。任何一种语言都可以通过任何一种方法访问数据。

Examples working stdin/stdout 在 python 中:

稍微偏离主题(取决于您对 "Perl" 的定义)但也许您感兴趣...

perl6 -e  ' .lc.say for "infile.txt".IO.lines ' > lowered.txt

这既不处理 "byte by byte" 也不处理 "whole file",而是处理 "line by line"。 .lines 创建一个惰性列表,因此如果您的文件很大,您将不会使用大量内存。该文件被假定为文本(意味着您在阅读时得到 Str 而不是 Buf 的字节)并且编码默认为 "Unicode" - 意思是 open将尝试找出使用的是什么 UTF,如果不能,它将假定 UTF-8。详情 here

默认情况下,当您阅读并放回 say 时,行尾是 chomp - 如果处理要求禁止这样做,您可以传递布尔值,命名参数 :chomp.lines(并使用 .print 而不是 .say);

$ perl6 -e  ' .lc.print for "infile.txt".IO.lines(:!chomp) ' > lowered.txt

您可以避免 IO 重定向并在 perl6 中完成所有操作,但这 将整个文件作为一个 Str;

读取
$ perl6 -e  ' "lowered.txt".IO.spurt: "infile.txt".IO.slurp.lc '

Python 3.x 等同于您的 Perl 代码可能如下所示:

#!/usr/bin/env python3.4
import sys

for line in sys.stdin:
    print(line[:-1].lower(), file=sys.stdout)

它逐行读取标准输入,可以在shell管道中使用

Python 程序将尝试读取整个输入文件。不带参数的 read() 调用将读取到 EOF,请参阅 the io module documentation

还有一个小bug,fout应该在"w"模式下打开。

如@denis-shatov 所述,可以编写 Python 相当于 Perl 脚本的脚本。

这里似乎有两个交错的问题,我首先解决这个问题。要了解如何使 Perl 和 Python 使用具有 非常 相似行为的任一调用,请参阅 post.

的第二部分

Short: 它们的不同之处在于 它们的工作方式 I/O 但两者都是逐行工作的,并且 Python 代码很容易更改,以允许与 Perl 代码相同的命令行调用。此外,两者都可以编写为允许从文件或标准输入流输入。


(1) 您的两个解决方案都是 "streaming,",因为它们都逐行处理输入。 Perl 代码从 STDIN 中读取,而 Python 代码从文件中获取数据,但它们一次都获取一行。从这个意义上说,它们在处理大文件时的效率相当。

在 Python 中逐行读取和写入文件的标准方法是

with open('infile', 'r') as fin, open('outfile', 'w') as fout:
    fout.write(fin.read().lower())

例如,参见 processing a very large file and read-and-write files. The way your read the file seems idiomatic for line-by-line processing, see for example SO posts on reading large-file line-by-line, on idiomatic line-by-line reading and another one on line-by-line reading 上的这些 SO post。

将此处的第一个打开更改为您的io.open直接将命令行的第一个参数作为文件名,并根据需要添加模式。

(2) 您显示的具有输入和输出重定向的命令行是一个 shell 功能

./program < input > output

program 通过标准输入流(文件描述符 0)馈送行。它们由 shell 通过其 < 重定向 从文件 input 提供。来自gnu bash manual(见3.6.1),其中“word”代表我们的“input

Redirection of input causes the file whose name results from the expansion of word to be opened for reading on file descriptor n, or the standard input (file descriptor 0) if n is not specified.

可以编写任何程序来做到这一点,即。充当 过滤器。对于 Python 你可以使用

import sys   
for line in sys.stdin:
    print line.lower()

参见 writing filters 上的 post 示例。现在您可以在 shell 中将其作为 script.py < input 调用。

代码 print 到标准输出,然后可以由 shell 使用 > 重定向。然后您将获得与 Perl 脚本相同的调用。

我认为标准输出重定向>在这两种情况下都很清楚。


最后,您可以通过这种方式使两者具有几乎相同的行为,并允许任一调用。

在Perl中,有如下成语

while (my $line = <>) {
    # process $line
}

钻石运算符 <>要么逐行从命令行提交的所有文件中获取(在@ARGV中找到),要么从 STDIN 获取它的行(如果数据以某种方式通过管道传输到脚本中)。来自 I/O Operators in perlop

The null filehandle <> is special: it can be used to emulate the behavior of sed and awk, and any other Unix filter program that takes a list of filenames, doing the same to each line of input from all of them. Input from <> comes either from standard input, or from each file listed on the command line. Here's how it works: the first time <> is evaluated, the @ARGV array is checked, and if it is empty, $ARGV[0] is set to "-" , which when opened gives you standard input. The @ARGV array is then processed as a list of filenames.

在 Python 中,您通过

获得几乎相同的行为
import fileinput
for line in fileinput.input():
    # process line

这也会遍历 sys.argv 中命名的文件行,如果列表为空,则默认为 sys.stdin。来自 fileinput 文档

This iterates over the lines of all files listed in sys.argv[1:], defaulting to sys.stdin if the list is empty. If a filename is '-', it is also replaced by sys.stdin. To specify an alternative list of filenames, pass it as the first argument to input(). A single file name is also allowed.

在这两种情况下,如果有文件名以外的命令行参数,则需要做更多的工作。

有了这个,您可以以任何一种方式使用 Perl 和 Python 脚本

lowercase < input > output
lowercase input   > output

或者,就此而言,如 cat input | lowercase > output


这里的所有方法都是逐行读取输入和写入输出。这可能会被解释器、系统和 shell 的重定向进一步优化(缓冲)。可以将其更改为以较小的块读取 and/or 写入,但这将非常低效并且会显着降低程序速度。

我在这里看到两个问题:

  1. 如何在不阅读整个文件的情况下将文本小写:逐行阅读
  2. 如果none如何处理命令行参数和默认标准输入:使用fileinput

方法如下:

要小写文本,只需使用 fin.readline() 或仅迭代文件对象(一次读取一行):

for line in fin:
    ...

要处理在命令行中指定的文件名,如果 none,则使用 stdin,使用 fileinput。如果您只是将所有内容发送到 stdout,这就足够了:

for line in fileinput.input():
    print(line.lower(), end="")

但是如果您想将大型语料库小写化并将结果存储到磁盘,您可能需要单独输出每个文件。这需要多做一些工作,因为 fileinput 不会自动重定向您的输出。这是一种方法:

currentname = None
for line in fileinput.input():
    if fileinput.isfirstline():
        if currentname and currentname != "<stdin>":  # clean up after previous file
            fout.close()

        currentname = fileinput.filename()        # Set up for new file
        if currentname == "<stdin>":
            fout = sys.stdout
        else:
            fout = open(currenttname+"-low", "w"
    fout.write(line.lower())

)

我将每个文件 <name> 写到 <name>-low,但你当然可以用任何其他方法替代(例如,使用相同的名称输出但在不同的目录中)。