Python subprocess “对象没有属性 'fileno'” 错误

7 投票
5 回答
21773 浏览
提问于 2025-04-15 11:12

这段代码在使用 Python 2.5.1 运行时,会出现“AttributeError: 'Popen' 对象没有 'fileno' 属性”的错误。

代码:

def get_blame(filename): 
    proc = []
    proc.append(Popen(['svn', 'blame', shellquote(filename)], stdout=PIPE))
    proc.append(Popen(['tr', '-s', r"'\040'"], stdin=proc[-1]), stdout=PIPE)
    proc.append(Popen(['tr', r"'\040'", r"';'"], stdin=proc[-1]), stdout=PIPE)
    proc.append(Popen(['cut', r"-d", r"\;", '-f', '3'], stdin=proc[-1]), stdout=PIPE)
    return proc[-1].stdout.read()

堆栈信息:

function walk_folder in blame.py at line 55
print_file(os.path.join(os.getcwd(), filename), path)

function print_file in blame.py at line 34
users = get_blame(filename)

function get_blame in blame.py at line 20
proc.append(Popen(['tr', '-s', r"'\040'"], stdin=proc[-1]), stdout=PIPE)

function __init__ in subprocess.py at line 533
(p2cread, p2cwrite,

function _get_handles in subprocess.py at line 830
p2cread = stdin.fileno()

这段代码应该是可以正常工作的,Python 文档中描述了 这种用法

5 个回答

1

你想要获取这个进程的标准输出,所以把你的 stdin=proc[-1] 改成 stdin=proc[-1].stdout

另外,你需要调整一下括号的位置,它应该放在 stdout 参数之后。

 proc.append(Popen(['tr', '-s', r"'\040'"], stdin=proc[-1]), stdout=PIPE)

应该是:

 proc.append(Popen(['tr', '-s', r"'\040'"], stdin=proc[-1].stdout, stdout=PIPE))

同样的方式修正你其他的 append 调用。

3

这个脚本里有一些奇怪的地方,

  • 你为什么要把每个进程都存储在一个列表里呢?直接用变量不是更容易理解吗?如果去掉所有的 .append(),会出现语法错误,因为你在多次调用 append 的时候,把 stdout=PIPE 传给了 append 的参数,而不是传给 Popen

    proc.append(Popen(...), stdout=PIPE)
    

    所以直接重写(虽然还有我稍后会提到的错误)会变成..

    def get_blame(filename): 
        blame = Popen(['svn', 'blame', shellquote(filename)], stdout=PIPE)
        tr1 = Popen(['tr', '-s', r"'\040'"], stdin=blame, stdout=PIPE)
        tr2 = Popen(['tr', r"'\040'", r"';'"], stdin=tr1), stdout=PIPE)
        cut = Popen(['cut', r"-d", r"\;", '-f', '3'], stdin=tr2, stdout=PIPE)
        return cut.stdout.read()
    
  • 在每个后续的命令中,你传递的是 Popen 对象,而不是那个进程的 stdout。根据 subprocess 文档中 “替代 shell 管道” 的部分,你应该这样做..

    p1 = Popen(["dmesg"], stdout=PIPE)
    p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
    

    ..而你做的相当于 stdin=p1

    上面重写代码中的 tr1 = 这一行应该变成..

    tr1 = Popen(['tr', '-s', r"'\040'"], stdin=blame.stdout, stdout=PIPE)
    
  • 你不需要用 subprocess 来转义命令/参数,因为 subprocess 并不会在任何 shell 中运行命令(除非你指定 shell=True)。可以查看 subprocess 文档中的 安全性部分。

    与其这样..

    proc.append(Popen(['svn', 'blame', shellquote(filename)], stdout=PIPE))
    

    ..你可以安全地这样做..

    Popen(['svn', 'blame', filename], stdout=PIPE)
    
  • 正如 S.Lott 建议的,不要用 subprocess 来处理那些在 Python 中更容易完成的文本操作(比如 tr/cut 命令)。首先,tr/cut 等命令并不是特别通用(不同版本有不同的参数),而且它们的可读性也很差(我完全不知道 tr 和 cut 在干什么)

    如果我要重写这个命令,我可能会这样做..

    def get_blame(filename): 
        blame = Popen(['svn', 'blame', filename], stdout=PIPE)
        output = blame.communicate()[0] # preferred to blame.stdout.read()
        # process commands output:
        ret = []
        for line in output.split("\n"):
            split_line = line.strip().split(" ")
            if len(split_line) > 2:
                rev = split_line[0]
                author = split_line[1]
                line = " ".join(split_line[2:])
    
                ret.append({'rev':rev, 'author':author, 'line':line})
    
        return ret
    
10

三件事

首先,你的 () 用得不对。

其次,subprocess.Popen() 的结果是一个进程对象,而不是一个文件。

proc = []
proc.append(Popen(['svn', 'blame', shellquote(filename)], stdout=PIPE))
proc.append(Popen(['tr', '-s', r"'\040'"], stdin=proc[-1]), stdout=PIPE)

proc[-1] 的值不是文件,而是包含这个文件的进程。

proc.append(Popen(['tr', '-s', r"'\040'"], stdin=proc[-1].stdout, stdout=PIPE))

第三,不要在命令行里搞那些 trcut 的东西,这样做会很慢。把 trcut 的处理写在 Python 里——这样更快也更简单。

撰写回答