Ложные новые строки, добавленные в командах управления Django

Запуск Django v1.10 на Python 3.5.0:

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 = ''

Но этот хак означает, что вы не получаете всех функций функции печати, вы должны использовать self.stdout.write и вручную подготовить байты.

Ответы

Ответ 1

При настройке self.stdout.ending явно команда печати работает так, как ожидалось.

Окончание строки должно быть установлено в self.stdout.ending, когда file=self.stdout, потому что это экземпляр 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)

Возвращает

hello world

Ответ 2

Как указано в Django 1.10 Команды пользовательского управления document:

Когда вы используете команды управления и хотите предоставить консольный вывод, вы должны написать self.stdout и self.stderr, вместо того чтобы печатать на stdout и stderr. Используя эти прокси-серверы, вам будет намного легче проверить свою пользовательскую команду. Также обратите внимание, что вам не нужно заканчивать сообщения символом новой строки, он будет добавлен автоматически, если вы не укажете конечный параметр:

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

Следовательно, чтобы напечатать в вашем классе Command, вы должны определить свою функцию 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(). Следовательно, лучше использовать параметр ending в функции self.stdout.write() (как показано в примере выше).

Как вы уже упоминали: "Но этот взлом означает, что вы не получаете всех функций функции печати, вы должны использовать self.stdout.write и вручную подготовить байты". Нет, вам не нужно беспокоиться о создании bytes или других функций print(), в качестве self.stdout.write() функции, принадлежащей OutputWrapper ожидает, что данные будут находиться в формате str. Также я хотел бы упомянуть, что print() и OutputWrapper.write() ведут себя очень похоже, как действуя как обертка вокруг sys.stdout.write().

Единственное различие между print() и OutputWrapper.write():

  • print() принимает строку сообщения как *args с параметром separator для объединения нескольких строк, тогда как
  • OutputWrapper.write() принимает строку с одним сообщением

Но эту разницу можно было бы легко обработать, явно связав строки с разделителем и передав их в OutputWrapper.write().

Вывод: Вам не нужно беспокоиться о дополнительных функциях, предоставляемых print(), поскольку их нет, и следует продолжать использовать self.stdout.write(), как это предлагается в этом ответе, цитируемый контент из Пользовательские команды управления.

Если вам интересно, вы можете проверить исходный код классов BaseCommand и OutputWrapper, доступных по адресу Исходный код для django.core.management.base. Это может помочь в устранении некоторых из ваших сомнений. Вы также можете проверить PEP-3105, связанный с Python 3 print().

Ответ 3

Прежде всего, self.stdout является экземпляром команды django.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.endswith(ending) завершится с ошибкой, если msg является экземпляром bytes и заканчивается как str.

Кроме того, print с self.stdout работает корректно, когда я устанавливаю явно self.stdout.ending = ''; однако это может означать, что другой код, который использует self.stdout.write, ожидая, что он вставляет новые строки, потерпит неудачу.


В вашем случае, что бы я сделал, это определить метод print для Command:

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')