2016-05-11 16 views
18

In Python, in un'operazione di numeri di tipo misto, il tipo più stretto è widened to that of the other, come ad esempio int + floatfloat:Perché data + timedelta diventa data, non datetime?

In [57]: 3 + 0.1 
Out[57]: 3.1 

Ma per datetime.date, abbiamo datetime.date + datetime.timedeltadatetime.date, nondatetime.datetime:

In [58]: datetime.date(2013, 1, 1) + datetime.timedelta(seconds=42) 
Out[58]: datetime.date(2013, 1, 1) 

Perché il ragionamento allargando applicato ai numeri, ma non t o date/datetime/timedelta?

(Sfondo: sto scrivendo una routine di lettura per un formato di file in cui un campo è anno, un campo è giorno dell'anno, un campo è millisecondi-da-mezzanotte. Naturalmente, la soluzione semplice ed esplicita è datetime.datetime(2013, 1, 1, 0, 0, 0) + datetime.timedelta(seconds=42), ma si potrebbe altrettanto ovvio che si dovrebbe riscrivere 3 + 0.1 come 3.0 + 0.1)

+2

Inoltre, quando si lavora con le date, non si vuole veramente aggiungere il tempo? Una data non ha un componente orario; non è la stessa cosa che aggiungere '.0' a un intero. Forse stavi lavorando con date * a mezzogiorno * invece? –

+0

Direi che * numeri * sono l'eccezione qui, in quanto è possibile forzare un intero a un valore float in base alle esigenze. Questo concetto non si espande automaticamente ad altri tipi. –

+0

@MartijnPieters Allora perché consentire quell'aggiunta in primo luogo? '[] +()' fallisce immediatamente. – user4815162342

risposta

-3

Quando si somma un timedelta a una data, pitone cercherà solo per l'attributo giorni del timedelta. Quindi se aggiungi 42 secondi, il giorno sarà 0 e non influenzerà la tua data.

+3

Posso vederlo, ma perché? – gerrit

5

Una data non è una sottoclasse di datetime; data/ora è un tipo composto , che combina uno date e un oggetto time in uno. Non è possibile decidere di produrre un tipo composto dalle operazioni su date qui; il componente time è non una frazione di una data.

D'altra parte, il Python numeric hierarchy definisce interi come un tipo di galleggiante specializzata (numbers.Integral è una sottoclasse indiretta di numbers.Real), e come tali operazioni di miscelazione tra interi e galleggia risultati nel tipo di base essendo prodotte. E un float è non un tipo composto, non esiste un tipo separato per la parte decimale del valore.

Se si desidera produrre il tipo composto da operazioni in date, sarà necessario essere esplicito (ed è meglio esplicito che implicito). Aggiungere un componente di tempo da soli:

datetime.combine(yourdate, time.min) + yourdelta 

dove yourdate potrebbe essere prodotto da una date.strptime('%Y %j') parsing del vostro anno più di input giorno-of-the-anno.

Le alternative (o la produzione di un oggetto datetimevolte in base al valore timedelta.seconds, o sempre) richiedono al programmatore di scartare nuovo componente data, se questo è tutto quello che si aspettavano.

6

L'oggetto timedelta non memorizza alcuna informazione sul fatto che riguardi o meno solo le date o anche le volte. (Il fatto che il numero di ore/minuti/secondi/micros sia 0 potrebbe essere solo una coincidenza!)

Quindi, supponiamo di avere qualcuno che vuole solo manipolare le date, ignorando i tempi, farebbe qualcosa come my_new_date = my_old_date + timedelta(days=1) . Sarebbe molto sorpresa e probabilmente infastidita nel constatare che my_new_date è ora un oggetto datetime anziché un oggetto date.

7

Il comportamento è documented:

date2 viene spostato in avanti nel tempo se timedelta.days > 0, o indietro se timedelta.days < 0. Successivamente date2 - date1 == timedelta.days. timedelta.seconds e timedelta.microseconds vengono ignorati.

(Il corsivo è mio. Questo comportamento è remained unchanged dal date oggetti sono stati aggiunti in Python 2.3.)

non sono stato in grado di trovare alcuna prova sul motivo per cui il modulo è progettato come questo. Sicuramente ci sono casi d'uso come il tuo dove vuoi rappresentare il punto temporale corrispondente alla mezzanotte all'inizio di una giornata. In questi casi è fastidioso dover convertire avanti e indietro. Ma ci sono altri casi d'uso in cui si desidera rappresentare un'intera giornata (e non solo un certo punto nel tempo in quel giorno), nel qual caso non si vuole finire accidentalmente con giorni parziali quando si aggiungono timedeltas.

Chris Withers ha suggerito che il comportamento essere cambiato, in issue 3249, ma Tim Peters ha osservato che:

una modifica incompatibile documentato il comportamento-questo-modo sempre lavorato è improbabile per essere accettato.

Se si desidera un oggetto che si comporta come un datetime.date, ma dove le operazioni aritmetiche ritornano datetime.datetime oggetti, quindi non dovrebbe essere non troppo difficile da scrivere uno:

from datetime import date, datetime, time, timedelta 

def _part_day(t): 
    """Return True if t is a timedelta object that does not consist of 
    whole days. 

    """ 
    return isinstance(t, timedelta) and (t.seconds or t.microseconds) 

class mydate(date): 
    """Subclass of datetime.date where arithmetic operations with a 
    timedelta object return a datetime.datetime object unless the 
    timedelta object consists of whole days. 

    """ 
    def datetime(self): 
     """Return datetime corresponding to the midnight at start of this 
     date. 

     """ 
     return datetime.combine(self, time()) 

    def __add__(self, other): 
     if _part_day(other): 
      return self.datetime() + other 
     else: 
      return super().__add__(other) 

    __radd__ = __add__ 

    def __sub__(self, other): 
     if _part_day(other): 
      return self.datetime() - other 
     else: 
      return super().__sub__(other) 

(Questo è testato, ma è non dovrebbe essere difficile a farlo funzionare da qui.)

+1

non è necessario sottoclasse 'date', [' dateutil.relativedelta' implementa il comportamento che @gerrit vuole.] (Http://stackoverflow.com/a/37169089/4279) – jfs

3

La ragione tecnica è probabile che i tipi built-in Python non tornano sottoclassi dai suoi gestori di regola vale a dire, date.__add__ non tornerà datetime. E il comportamento coerente richiederebbe date e datetime di essere intercambiabili (non lo sono).

date + timedelta il comportamento è documentato e non cambierà. Se si desidera datetime di conseguenza; creare datetime dalla data d:

dt = datetime(d.year, d.month, d.day) 

Tecnicamente, date.__add__ avrebbe potuto delegato il lavoro per timedelta.__radd__. timedelta memorizza separatamente days e quindi è semplice ed efficiente scoprire se rappresenta un numero intero di giorni, ad esempio, potremmo ottenere date o datetime da date + timedelta (ciò non significa che dovremmo).

Il problema è che 1 e 1.0 sono uguali timedelta(1) in questo caso ovvero se dovessimo permettere date + timedelta per tornare datetime allora dovrebbe restituire datetime per tutti i valori se si considerano soltanto tipi.

C'è stato un precedente quando int + int ha restituito int o long a seconda del risultato, ad es., l'operazione con gli stessi tipi può restituire valori di tipi diversi a seconda solo dei valori di input. Sebbene date e datetime non siano intercambiabili tanto quanto lo e lo long.

date + timedelta tornare date per alcuni valori di timedelta e datetime per gli altri potrebbero creare confusione se non introduciamo date(y,m,d) == datetime(y,m,d) troppo (come 1 == 1.0) o date.today() < datetime.now() da the related Python issue mentioned by @Gareth Rees (come 1 < 1.1). datetime essendo una sottoclasse suggerisco questo percorso anche se ho sentito un argomento che è stato un errore rendere la sottoclasse a date.

il comportamento desiderato è implementato in dateutil pacchetto:

>>> from datetime import date 
>>> from dateutil.relativedelta import relativedelta 
>>> date.today() + relativedelta(days=1) 
datetime.date(2016, 5, 12) 
>>> date.today() + relativedelta(days=1, seconds=1) 
datetime.datetime(2016, 5, 12, 0, 0, 1) 

time() + timedelta non è valido, quindi combine() non mi aiuta qui.

combine() opere datetime.combine(d, time()) + timedelta_obj. Però, potresti scriverlo come: datetime(d.year, d.month, d.day) + timedelta_obj.

Naturalmente, la soluzione semplice ed esplicito è datetime(2013, 1, 1, 0, 0, 0) + timedelta(seconds=42), ma si potrebbe anche riscrivere 3 + 0.1 come 3.0 + 0.1)

int + float è sempre float:

>>> 3 + 1.0 
4.0 
>>> 3 + 1 
4 

differenza 1.0 e 1; type(timedelta(1.0)) == type(timedelta(1)) (stesso tipo oltre ad essere uguale).

Sto scrivendo una routine di lettura per un formato di file in cui un campo è un anno, un campo è giorno dell'anno, un campo è millisecondi-da-mezzanotte.

dt = datetime(year, 1, 1) + timedelta(days=day_of_year - 1, milliseconds=ms) 
Problemi correlati