2016-05-23 37 views
15

esecuzione Django v1.10 su Python 3.5.0:newlines spurie aggiunto nella gestione Django comandi

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) 

uscita prevista:

hello world 

uscita effettiva:

hello 

world 

Come fare Passo correttamente il carattere finale? Attualmente uso una soluzione di impostare in modo esplicito:

self.stdout.ending = '' 

Ma questo hack significa che non si ottiene tutte le caratteristiche della funzione di stampa, è necessario utilizzare self.stdout.write e preparare il byte manualmente.

+0

Il tuo caso d'uso è più lungo delle linee di creazione di un'intera stringa di testo basata su elementi concomitanti ecc. La ragione per cui lo chiedo è, se l'intera stringa è conosciuta in anticipo, come nel caso di "Ciao mondo" tu potrebbe essere facilmente scritto come una linea completa mentre si utilizza la stampa senza stdout.write per avvalersi del controllo sul carattere finale di fine riga. Pertanto, supponendo che non si conosca il testo in anticipo, non è possibile creare il testo aggiungendo le stringhe finché il testo completo non è pronto per la stampa. –

+1

correlati: http://stackoverflow.com/questions/31926422/redirection-in-django-command-called-from-another-command-results-in-est-aneous. E se non trovi una soluzione, ti consiglio di scrivere un decoratore o un gestore di contesto che rattoppi temporaneamente 'print' e/o' self.stdout' per comportarsi nel modo desiderato. –

+0

Non capisco come usare 'self.stdout.ending = ''" "impedisce a [tu] di usare la funzione di stampa e tutte le sue belle funzionalità". Puoi ancora usare 'print', e l'output sarà quello che ti aspetti, qual è il problema? – Louis

risposta

5

Quando si imposta self.stdout.ending esplicitamente, il funzionamento del comando di stampa come previsto.

Il numero di riga deve essere impostato su self.stdout.ending quando file=self.stdout, poiché questa è un'istanza di 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 
+0

Hai ragione ... non sono sicuro di come sono riuscito a mancarlo! – wim

5

Prima di tutto, self.stdout è un'istanza del comando django.core.management.base.OutputWrapper. La sua write aspetta un str, non bytes, in tal modo è possibile utilizzare

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

realtà self.stdout.write non accetta byte, ma solo quando il endingè una stringa vuota - che è perché il suo metodo write è definito

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

Se ending è vero, allora msg.endswith(ending) avrà esito negativo se msg è un'istanza bytes e un finale è un str.

Inoltre, print con self.stdout funziona correttamente quando imposto lo self.stdout.ending = '' in modo esplicito; tuttavia, ciò potrebbe significare che un altro codice che utilizza self.stdout.write in attesa di inserimento di nuove righe non riuscirebbe.


Nel tuo caso, cosa farei è quello di definire un metodo per la printCommand:

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

È possibile effettuare questo nel proprio BaseCommand sottoclasse - e lo si può utilizzare con diversi i file troppo:

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

Come menzionato nella Django 1.10's Custom Management Commands documento:

Quando si utilizza i comandi di gestione e si desidera fornire l'output su console, si dovrebbe scrivere a self.stdout e self.stderr, invece di stampare su stdout e stderr direttamente. Utilizzando questi proxy, diventa molto più facile testare il comando personalizzato. Si noti inoltre che non è necessario per terminare i messaggi con un carattere di nuova riga, verrà aggiunto automaticamente, a meno che non si specifica il parametro finale:

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

Quindi, al fine di stampare nella classe Command , è necessario definire la funzione handle() come:

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 

Inoltre, impostando in modo esplicito self.stdout.ending = '', si sta modificando la proprietà di self.stdout oggetto. Ma potresti non volere che questo si rifletta nelle chiamate future di self.stdout.write(). Quindi sarà meglio usare il parametro ending all'interno della funzione self.stdout.write() (come dimostrato nel codice di esempio sopra).

Come hai detto "Ma questo hack significa che non ottieni tutte le funzionalità della funzione di stampa, devi usare self.stdout.write e preparare i byte manualmente." No, non devi preoccuparti di creare bytes o altre funzionalità di print(), come la funzione self.stdout.write() appartenente alla classe OutputWrapper si aspetta che i dati siano nel formato str. Vorrei anche ricordare che lo print() e lo OutputWrapper.write() si comportano in modo abbastanza simile, sia agendo come wrapper attorno allo sys.stdout.write().

L'unica differenza tra print() e OutputWrapper.write() è:

  • print() accetta stringa di messaggio come *args con separator parametro per unire le molteplici corde, mentre
  • OutputWrapper.write() accetta stringa di messaggio singolo

Ma questa differenza potrebbe essere facilmente gestita unendo esplicitamente le stringhe con sep arator e passarlo a OutputWrapper.write().

Conclusione: Non devi preoccuparti per le caratteristiche aggiuntive fornite dal print() come non ce ne sono, e dovrebbe andare avanti con l'utilizzo di self.stdout.write() come suggerito nel contenuto citato di questa risposta da Custom Management Commands documento.

Se sei interessato, puoi verificare il codice sorgente delle classi BaseCommand e OutputWrapper disponibili al numero: Source code for django.core.management.base. Potrebbe aiutare a chiarire alcuni dei tuoi dubbi. È anche possibile controllare lo PEP-3105 relativo a Python 3 print().

+2

Grazie, controlla le tue richieste! Questa progettazione di "OutputWrapper" è stata una decisione sbagliata, causa 'self.stdout' di violare il principio della sorpresa minima. Ti aspetti che si comporti come 'sys.stdout', ma non lo fa, il che significa che non puoi usare' self.stdout' insieme alla funzione di stampa correttamente, a meno che tu non sia RTFM. Questo mi ha sorpreso a testare alcune volte, ora capisco finalmente le ragioni! Imho l'interfaccia avrebbe dovuto corrispondere a 'sys.stdout'. O quello, o dovremmo avere un wrapper chiamato 'self.print' invece. – wim

+0

Ho già avuto l'idea che tu stia considerando 'self.stdout' come' sys.stdout', ecco perché ho menzionato * "la funzione self.stdout.write() che appartiene alla classe OutputWrapper si aspetta che i dati siano nel formato str. vorrei ricordare che print() e OutputWrapper.write() si comportano in modo abbastanza simile sia agendo come un wrapper attorno a sys.stdout.write(). "* –