从另一个命令调用的 Django 命令中的重定向会导致无关的换行符。我怎样才能解决这个问题?
Redirection in Django command called from another command results in extraneous newlines. How can I fix this?
设置
为了说明问题,我在我的项目中创建了这些命令:
foo.py
:
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write("foo")
# I pass `self.stdout` here explicitly because if `foo` is
# redirected, I want `baz` redirected too.
call_command('baz', stdout=self.stdout)
baz.py
:
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
def handle(self, *args, **options):
# This could be reduced to one call to self.stdout.write
# but this code is meant to minimally reproduce what happens in a
# complex command where multiple self.stdout.write calls are
# made. If the code here were replaced with a single call, it
# would cease to reproduce the issue.
self.stdout.write("baz ", ending='')
# Imagine a lot of stuff happening here with conditionals and
# loops.
self.stdout.write("baz")
实际行为
我运行foo
是这样的:
./manage.py foo
我将此输出到控制台:
foo
baz
baz
期望的行为
我想要的是控制台的输出是:
foo
baz baz
请注意,当我直接使用 ./manage.py baz
调用 baz
时,我得到以下输出:
baz baz
两者之间没有换行"baz"。当通过 foo
.
调用 baz
时,我想要相同的布局
问题
它不起作用的原因是 Django 使用 OutputWrapper
对象来包装作为 Command
的 stdout
参数传递的任何内容。该对象在命令的方法中变为 self.stdout
。 (这在 Django 1.8 中是正确的,据我所知,早在 Django 1.5 中。)
OutputWrapper
有一个 write
方法,默认情况下会在写入输出的内容中添加换行符。您可以使用 ending=''
将其关闭,这就是您所做的并且在直接调用 baz
时工作正常。但是,OutputWrapper
对象并不期望它会包装另一个 OutputWrapper
对象。当您的 baz
代码通过 foo
调用并执行 self.stdout.write("baz ", ending='')
时,它会在它包装的对象上调用 write
,但不会转发 ending=''
参数。因此,为 foo
创建的 OutputWrapper
在没有 ending=''
的情况下被调用,并且换行符输出到控制台。
解决方案
我更喜欢的解决方案是在我的代码中完全复制 Django 在决定 OutputWrapper
应该包装什么时所做的事情:
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write("foo\n")
call_command('baz', stdout=options.get('stdout', sys.stdout))
如果 foo
没有 stdout
关键字参数,stdout=options.get('stdout', sys.stdout)
位将通过 sys.stdout
。否则,它转发 stdout
关键字参数。您可以通过将 stdout
的所有实例更改为 stderr
.
来对 stderr
执行相同的操作
另一种方法是将 OutputWrapper
的结尾设置为 ''
,如下所示:
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.ending = ''
self.stdout.write("foo\n")
call_command('baz')
然后您必须在编写命令的同时牢记您始终必须显式输出换行符:这就是为什么我们现在有 self.stdout.write("foo\n")
,在字符串末尾有一个换行符。这样做的好处是 baz
输出的任何内容都会立即出现在控制台上,因此如果它在某些输出后挂起,您至少可以使用一些东西。但是,OutputWrapper
不是已记录为 Django 项目直接使用的 class。这个解决方案基本上使用了一个 API,它可以在较新版本的 Django 中毫无警告地改变。
设置
为了说明问题,我在我的项目中创建了这些命令:
foo.py
:
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write("foo")
# I pass `self.stdout` here explicitly because if `foo` is
# redirected, I want `baz` redirected too.
call_command('baz', stdout=self.stdout)
baz.py
:
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
def handle(self, *args, **options):
# This could be reduced to one call to self.stdout.write
# but this code is meant to minimally reproduce what happens in a
# complex command where multiple self.stdout.write calls are
# made. If the code here were replaced with a single call, it
# would cease to reproduce the issue.
self.stdout.write("baz ", ending='')
# Imagine a lot of stuff happening here with conditionals and
# loops.
self.stdout.write("baz")
实际行为
我运行foo
是这样的:
./manage.py foo
我将此输出到控制台:
foo
baz
baz
期望的行为
我想要的是控制台的输出是:
foo
baz baz
请注意,当我直接使用 ./manage.py baz
调用 baz
时,我得到以下输出:
baz baz
两者之间没有换行"baz"。当通过 foo
.
baz
时,我想要相同的布局
问题
它不起作用的原因是 Django 使用 OutputWrapper
对象来包装作为 Command
的 stdout
参数传递的任何内容。该对象在命令的方法中变为 self.stdout
。 (这在 Django 1.8 中是正确的,据我所知,早在 Django 1.5 中。)
OutputWrapper
有一个 write
方法,默认情况下会在写入输出的内容中添加换行符。您可以使用 ending=''
将其关闭,这就是您所做的并且在直接调用 baz
时工作正常。但是,OutputWrapper
对象并不期望它会包装另一个 OutputWrapper
对象。当您的 baz
代码通过 foo
调用并执行 self.stdout.write("baz ", ending='')
时,它会在它包装的对象上调用 write
,但不会转发 ending=''
参数。因此,为 foo
创建的 OutputWrapper
在没有 ending=''
的情况下被调用,并且换行符输出到控制台。
解决方案
我更喜欢的解决方案是在我的代码中完全复制 Django 在决定 OutputWrapper
应该包装什么时所做的事情:
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write("foo\n")
call_command('baz', stdout=options.get('stdout', sys.stdout))
如果 foo
没有 stdout
关键字参数,stdout=options.get('stdout', sys.stdout)
位将通过 sys.stdout
。否则,它转发 stdout
关键字参数。您可以通过将 stdout
的所有实例更改为 stderr
.
stderr
执行相同的操作
另一种方法是将 OutputWrapper
的结尾设置为 ''
,如下所示:
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.ending = ''
self.stdout.write("foo\n")
call_command('baz')
然后您必须在编写命令的同时牢记您始终必须显式输出换行符:这就是为什么我们现在有 self.stdout.write("foo\n")
,在字符串末尾有一个换行符。这样做的好处是 baz
输出的任何内容都会立即出现在控制台上,因此如果它在某些输出后挂起,您至少可以使用一些东西。但是,OutputWrapper
不是已记录为 Django 项目直接使用的 class。这个解决方案基本上使用了一个 API,它可以在较新版本的 Django 中毫无警告地改变。