在 Django 管理命令中添加虚假换行符

Spurious newlines added in Django management commands

运行 Python 3.5.0 上的 Django v1.10:

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    def handle(self, *args, **options):
        print('hello ', end='', file=self.stdout)
        print('world', file=self.stdout)

预期输出:

hello world

实际输出:

hello 

world

如何正确传递结束符?我目前使用明确设置的解决方法:

 self.stdout.ending = ''

但是这个 hack 意味着您无法获得打印功能的所有功能,您必须使用 self.stdout.write 并手动准备字节。

首先,self.stdoutdjango.core.management.base.OutputWrapper命令的一个实例。它的 write 需要 str,而不是 bytes,因此您可以使用

self.stdout.write('hello ', ending='')
self.stdout.write('world')

实际上 self.stdout.write 确实接受字节,但仅当 ending 空字符串时 - 这是因为其 write 方法已定义

def write(self, msg, style_func=None, ending=None):
    ending = self.ending if ending is None else ending
    if ending and not msg.endswith(ending):
        msg += ending
    style_func = style_func or self.style_func
    self._out.write(force_str(style_func(msg)))

如果 ending 为真,如果 msg 是一个 bytes 实例且结尾是 str,则 msg.endswith(ending) 将失败。

此外,当我明确设置 self.stdout.ending = '' 时,printself.stdout 确实可以正常工作;然而,这样做可能意味着其他使用 self.stdout.write 期望它插入换行符的代码会失败。


在你的情况下,我要做的是为 Command:

定义一个 print 方法
from django.core.management.base import OutputWrapper

class PrintHelper:
    def __init__(self, wrapped):
        self.wrapped = wrapped

    def write(self, s):
        if isinstance(self.wrapped, OutputWrapper):
            self.wrapped.write(s, ending='')
        else:
            self.wrapped.write(s)

class Command(BaseCommand):
    def print(self, *args, file=None, **kwargs):
        if file is None:
            file = self.stdout
        print(*args, file=PrintHelper(file), **kwargs)

    def handle(self, *args, **options):
        self.print('hello ', end='')
        self.print('world')

您可以将它变成您自己的 BaseCommand 子类 - 您也可以将它用于不同的文件:

    def handle(self, *args, **options):
        for c in '|/-\' * 100:
            self.print('\rhello world: ' + c, end='', file=self.stderr)
            time.sleep(0.1)
        self.print('\bOK')

显式设置 self.stdout.ending 时,打印命令按预期工作。

file=self.stdout时,需要在self.stdout.ending中设置行结束符,因为那是django.core.management.base.OutputWrapper的实例。

class Command(BaseCommand):
    def handle(self, *args, **options):
        self.stdout.ending = ''
        print('hello ', end='', file=self.stdout)
        print('world', file=self.stdout)

Returns

hello world

Django 1.10's Custom Management Commands 文档中所述:

When you are using management commands and wish to provide console output, you should write to self.stdout and self.stderr, instead of printing to stdout and stderr directly. By using these proxies, it becomes much easier to test your custom command. Note also that you don’t need to end messages with a newline character, it will be added automatically, unless you specify the ending parameter:

self.stdout.write("Unterminated line", ending='')

因此,为了在 Command class 中打印,您应该将 handle() 函数定义为:

from django.core.management.base import BaseCommand

class Command(BaseCommand):
    def handle(self, *args, **options):
        self.stdout.write("hello ", ending='')
        self.stdout.write("world", ending='')

# prints: hello world

此外,通过显式设置 self.stdout.ending = '',您正在修改 self.stdout 对象的 属性。但您可能不希望这反映在以后对 self.stdout.write() 的调用中。因此,最好在 self.stdout.write() 函数中使用 ending 参数(如上面的示例代码所示)。

正如您提到的 "But this hack means you don't get all the features of the print function, you must use self.stdout.write and prepare the bytes manually." 不,您不必担心创建 bytesprint() 的其他功能,因为self.stdout.write() function belonging to OutputWrapper class expects data to be in str format. Also I would like to mention that print() and OutputWrapper.write() behaves quite similar both acting as a wrapper around sys.stdout.write()

print()OutputWrapper.write()的唯一区别是:

  • print() 接受消息字符串作为 *argsseparator 参数来连接多个字符串,而
  • OutputWrapper.write() 接受单个消息字符串

但是通过使用 分隔符 显式连接字符串并将其传递给 OutputWrapper.write().

可以轻松处理这种差异

结论:您不必担心 print() 提供的附加功能,因为有 none,应该继续使用 self.stdout.write() 根据此答案的引用内容从 Custom Management Commands 文档中建议。

如果您有兴趣,可以查看 BaseCommandOutputWrapper classes 的源代码,网址为:Source code for django.core.management.base. It might help in clearing some of your doubts. You may also check PEP-3105 related to Python 3's print().