el operador " is " se comporta inesperadamente con enteros


¿Por qué lo siguiente se comporta inesperadamente en Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?
>>> 257 is 257
True           # Yet the literal numbers compare properly

Estoy usando Python 2.5.2. Probando algunas versiones diferentes de Python, parece que Python 2.3.3 muestra el comportamiento anterior entre 99 y 100.

Basado en lo anterior, puedo suponer que Python está implementado internamente de tal manera que los enteros "pequeños" se almacenan de una manera diferente a los enteros más grandes y el operador is puede notar la diferencia. ¿Por qué la abstracción permeable? ¿Cuál es una mejor manera de comparar dos objetos arbitrarios para ver si son los mismos cuando no sé de antemano si son números o no?

Author: Jim Fasarakis Hilliard, 2008-11-20

11 answers

Echa un vistazo a esto:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

EDITAR: Esto es lo que encontré en la documentación de Python 2, "Objetos enteros planos" (Es lo mismo para Python 3):

La implementación actual mantiene una matriz de objetos enteros para todos enteros entre -5 y 256, cuando crear un int en ese rango que en realidad, sólo obtener una referencia a el objeto existente. Así que debería ser posible cambiar el valor de 1. Me sospechar el comportamiento de Python en este caso no está definido. :-)

 308
Author: Cybis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-31 18:45:36

El operador "is" de Python se comporta inesperadamente con enteros?

En resumen - permítanme enfatizar: No utilice is para comparar enteros.

Este no es un comportamiento sobre el que deba tener expectativas.

En su lugar, use == y != para comparar la igualdad y la desigualdad, respectivamente. Por ejemplo:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Explicación

Para saber esto, necesita saber lo siguiente.

Primero, ¿qué hace is hacer? Es un operador de comparación. De la documentación :

Los operadores is y is not prueban la identidad del objeto: x is y es true si y solo si x e y son el mismo objeto. x is not y produce la valor inverso de la verdad.

Y así los siguientes son equivalentes.

>>> a is b
>>> id(a) == id(b)

De la documentación :

id Devuelve la "identidad" de un objeto. Este es un entero (o largo entero) que es garantizado para ser único y constante para este objeto durante su vida. Dos objetos con vidas no superpuestas pueden tienen el mismo valor id().

Tenga en cuenta que el hecho de que el id de un objeto en CPython (la implementación de referencia de Python) sea la ubicación en memoria es un detalle de implementación. Otras implementaciones de Python (como Jython o IronPython) podrían fácilmente tener una implementación diferente para id.

Entonces, ¿cuál es el caso de uso para is? PEP8 describe :

Las comparaciones con singletons como None siempre deben hacerse con is o is not, nunca los operadores de igualdad.

La pregunta

Usted hace, y declara, la siguiente pregunta (con código):{[49]]}

¿Por qué lo siguiente se comporta inesperadamente en Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

Es no un resultado esperado. ¿Por qué se espera? Solo significa que los enteros valorados en 256 referenciado por a y b son la misma instancia de integer. Los enteros son inmutables en Python, por lo que no pueden cambiar. Esto no debería tener ningún impacto en ningún código. No debería esperarse. Se trata simplemente de un detalle de aplicación.

Pero quizás deberíamos alegrarnos de que no haya una nueva instancia separada en la memoria cada vez que declaramos que un valor es igual a 256.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Parece que ahora tenemos dos instancias separadas de enteros con el valor de 257 en memoria. Dado que los enteros son inmutables, esto desperdicia memoria. Esperemos no desperdiciar mucho. Probablemente no. Pero este comportamiento no está garantizado.

>>> 257 is 257
True           # Yet the literal numbers compare properly

Bueno, esto parece que su implementación particular de Python está tratando de ser inteligente y no crear números enteros redundantemente valorados en la memoria a menos que tenga que hacerlo. Parece indicar que está utilizando la implementación de referencia de Python, que es CPython. Bien por CPython.

Podría ser aún mejor si CPython pudiera hacer esto globalmente, si pudiera hacerlo a bajo costo (ya que habría un costo en la búsqueda), tal vez otra implementación podría hacerlo.

Pero en cuanto al impacto en el código, no debería importarle si un entero es una instancia particular de un entero. Solo debería importarle cuál es el valor de esa instancia, y usaría los operadores de comparación normales para eso, es decir, ==.

Qué hace is

is comprueba que el id de dos objetos es el mismo. En CPython, el id es la ubicación en memoria, pero podría ser algún otro número de identificación única en otra implementación. Para repetir esto con código:

>>> a is b

Es lo mismo que

>>> id(a) == id(b)

¿Por qué querríamos usar is entonces?

Esto puede ser una comprobación muy rápida en relación a decir, comprobar si dos cadenas muy largas son iguales en valor. Pero dado que se aplica a la singularidad del objeto, tenemos casos de uso limitados para él. De hecho, sobre todo queremos usarlo para comprobar None, que es un singleton (una instancia única que existe en un lugar de la memoria). Podríamos crear otros singletons si hay potencial para combinarlos, lo que podríamos comprobar con is, pero estos son relativamente raros. Aquí hay un ejemplo (funcionará en Python 2 y 3) por ejemplo,

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Que imprime:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

Y así vemos, con is y un centinela, somos capaces de diferenciar entre cuando bar se llama sin argumentos y cuando se llama con None. Estos son los principales use-cases for is - do not úselo para probar la igualdad de enteros, cadenas, tuplas u otras cosas como estas.

 64
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-09 20:02:03

Depende de si usted está buscando para ver si 2 cosas son iguales, o el mismo objeto.

is comprueba si son el mismo objeto, no solo iguales. Los pequeños ints probablemente apuntan a la misma ubicación de memoria para la eficiencia del espacio

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Debe usar == para comparar la igualdad de objetos arbitrarios. Puede especificar el comportamiento con los atributos __eq__ y __ne__.

 55
Author: JimB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-24 02:20:18

Como se puede comprobar en archivo fuente intobject.c, Python almacena en caché números enteros pequeños para mayor eficiencia. Cada vez que crea una referencia a un entero pequeño, se refiere al entero pequeño almacenado en caché, no a un objeto nuevo. 257 no es un entero pequeño, por lo que se calcula como un objeto diferente.

Es mejor usar == para ese propósito.

 36
Author: Angel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-24 02:20:25

Llego tarde pero, ¿quieres alguna fuente con tu respuesta?*

Lo bueno de CPython es que en realidad se puede ver la fuente de esto. Voy a usar enlaces para la versión 3.5 por ahora; encontrar los 2.x correspondientes es trivial.

En CPython, la función C-API que maneja la creación de un nuevo objeto int es PyLong_FromLong(long v). La descripción de esta función es:

La implementación actual mantiene un array de objetos enteros para todos los enteros entre -5 y 256, cuando crea un int en ese rango, en realidad solo obtiene una referencia al objeto existente . Así que debería ser posible cambiar el valor de 1. Sospecho que el comportamiento de Python en este caso es indefinido. :-)

No sé acerca de ti, pero veo esto y pienso: Vamos a encontrar esa matriz!

Si no has jugado con el código C implementando CPython deberías , todo es bastante organizado y legible. Para nuestro caso, tenemos que buscar en el Objects/ subdirectorio del árbol de directorios del código fuente .

PyLong_FromLong trata con long objetos por lo que no debería ser difícil deducir que necesitamos mirar dentro longobject.c. Después de mirar dentro, puede que pienses que las cosas son caóticas; lo son, pero no temas, la función que estamos buscando es escalofriante line 230 esperando a que lo comprobemos. Es una función pequeña por lo que la principal body (excluyendo declaraciones) se pega fácilmente aquí:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

Ahora, no estamos C master-code-haxxorz pero tampoco somos tontos, podemos ver que CHECK_SMALL_INT(ival); mirando a todos nos seduce; podemos entender que tiene algo que ver con esto. Vamos a comprobarlo:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

Entonces es una macro que llama a la función get_small_int si el valor ival satisface la condición:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Entonces, ¿qué son NSMALLNEGINTS y NSMALLPOSINTS? Si adivinas macros no obtienes nada porque eso no fue una pregunta tan difícil.. de todos Modos, aquí están:

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

Así que nuestra condición es if (-5 <= ival && ival < 257) llamar get_small_int.

No hay otro lugar para ir que no sea continuar nuestro viaje mirando get_small_int en todo su esplendor (bueno, solo miraremos su cuerpo porque así son las cosas interesantes):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Está bien, declarar un PyObject, afirmar que la condición anterior se mantiene y ejecutar la asignación:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_ints se parece mucho a eso. matriz que hemos estado buscando.. ¡y lo es! ¡Podríamos haber leído la maldita documentación y lo habríamos sabido todo el tiempo!:

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Así que sí, este es nuestro hombre. Cuando desee crear un nuevo int en el rango [NSMALLNEGINTS, NSMALLPOSINTS), simplemente obtendrá una referencia a un objeto ya existente que ha sido preasignado.

Dado que la referencia se refiere al mismo objeto, emitir id() directamente o verificar la identidad con is en él devolverá exactamente el mismo cosa.

Pero, ¿cuándo se asignan??

Durante la inicialización en _PyLong_Init Python con mucho gusto entrará en un bucle for haga esto por usted:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {
    // Look me up!
}

Espero que mi explicación te haya hecho C (juego de palabras obviamente intencionado) cosas claramente ahora.


Pero, 257 es 257? ¿Qué pasa?

Esto es en realidad más fácil de explicar, y ya he intentado hacerlo; es debido al hecho de que Python ejecutará este interactivo declaración:

>>> 257 is 257

Como un solo bloque. Durante la complilación de esta declaración, CPython verá que tiene dos literales coincidentes y utilizará el mismo PyLongObject que representa 257. Puedes ver esto si haces la compilación tú mismo y examinas su contenido:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Cuando CPython hace la operación; ahora solo va a cargar exactamente el mismo objeto: {[48]]}

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Así que is volverá True.


* -- Voy a tratar de decir esto en un más manera introductoria para que la mayoría pueda seguir adelante.

 32
Author: Jim Fasarakis Hilliard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-31 23:53:19

Creo que sus hipótesis son correctas. Experimenta con id (identidad del objeto):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Parece que los números <= 255 son tratados como literales y todo lo anterior es tratado de manera diferente!

 18
Author: Amit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-08 10:12:22

Para objetos de valor inmutable, como ints, strings o datetimes, la identidad del objeto no es especialmente útil. Es mejor pensar en la igualdad. La identidad es esencialmente un detalle de implementación para objetos de valor, ya que son inmutables, no hay diferencia efectiva entre tener varias referencias al mismo objeto o varios objetos.

 12
Author: babbageclunk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-21 01:58:53

is es el operador de igualdad de identidad (funcionando como id(a) == id(b)); es solo que dos números iguales no son necesariamente el mismo objeto. Por razones de rendimiento algunos enteros pequeños pasan a ser memoized por lo que tenderán a ser los mismos (esto se puede hacer ya que son inmutables).

PHP === operador, por otro lado, se describe como comprobar la igualdad y el tipo: x == y and type(x) == type(y) según el comentario de Paulo Freitas. Esto será suficiente para los números comunes, pero difieren de is para las clases que definen __eq__ de una manera absurda:

class Unequal:
    def __eq__(self, other):
        return False

PHP aparentemente permite lo mismo para las clases "integradas" (que tomo como implementadas a nivel C, no en PHP). Un uso un poco menos absurdo podría ser un objeto temporizador, que tiene un valor diferente cada vez que se utiliza como un número. Bastante por qué querrías emular Visual Basic Now en lugar de mostrar que es una evaluación con time.time() No lo sé.

Greg Hewgill (OP) hizo una aclaración comentario " Mi objetivo es comparar la identidad del objeto, en lugar de la igualdad de valor. Excepto por los números, donde quiero tratar la identidad del objeto de la misma manera que la igualdad de valor."

Esto tendría otra respuesta, ya que tenemos que categorizar las cosas como números o no, para seleccionar si comparamos con == o is. CPython define el protocolo de números , incluyendo PyNumber_Check, pero esto no es accesible desde Python.

Podríamos intentar usar isinstance con todos los tipos de números que conocemos, pero esto sería inevitablemente incompleto. El módulo types contiene una lista de StringTypes pero no NumberTypes. Desde Python 2.6, las clases numéricas integradas tienen una clase basenumbers.Number, pero tiene el mismo problema:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

Por cierto, NumPy producirá instancias separadas de números bajos.

En realidad no sé una respuesta a esta variante de la pregunta. Supongo que uno podría teóricamente usar ctypes para llamar PyNumber_Check, pero incluso esa función ha sido debatida , y ciertamente no es portátil. Tendremos que ser menos particulares sobre lo que probamos por ahora.

Al final, este problema se debe a que Python no tenía originalmente un árbol de tipos con predicados como Scheme number?, o Haskell's tipo clase Num . is comprueba la identidad del objeto, no la igualdad de valor. PHP también tiene una historia colorida, donde === aparentemente se comporta como is solo en objetos en PHP5, pero no PHP4 . Tales son los dolores de crecimiento de moverse a través de idiomas (incluyendo versiones de uno).

 8
Author: Yann Vernier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-12 11:52:54

También sucede con cadenas:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Ahora todo parece estar bien.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Eso también se espera.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

Eso es inesperado.

 4
Author: sobolevn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-14 15:53:05

Echa un vistazo aquí

La implementación actual mantiene una matriz de objetos enteros para todos enteros entre -5 y 256, cuando se crea un int en ese rango que en realidad, solo tienes que volver una referencia al objeto existente.

 3
Author: user5319825,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-18 12:46:27

Hay otro problema que no se señala en ninguna de las respuestas existentes. Python puede combinar dos valores inmutables, y los valores int pequeños pre-creados no son la única forma en que esto puede suceder. Una implementación de Python nunca está garantizada para hacer esto, pero todos lo hacen para algo más que pequeños ints.


Por un lado, hay algunos otros valores pre-creados, como el vacío tuple, str, y bytes, y algunas cadenas cortas (en CPython 3.6, es el 256 latin-1 cuerdas). Por ejemplo:

>>> a = ()
>>> b = ()
>>> a is b
True

Pero también, incluso los valores no pre-creados pueden ser idénticos. Considere estos ejemplos:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

Y esto no se limita a int valores:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Obviamente, CPython no viene con un valor pre-creado float para 42.23e100. Entonces, ¿qué está pasando aquí?

El compilador CPython fusionará valores constantes de algunos tipos conocidos-inmutables como int, float, str, bytes, en la misma unidad de compilación. Para un módulo, todo el módulo es una unidad de compilación, pero en el intérprete interactivo, cada instrucción es una unidad de compilación separada. Dado que c y d se definen en sentencias separadas, sus valores no se fusionan. Dado que e y f se definen en la misma instrucción, sus valores se fusionan.


Puedes ver lo que está pasando desmontando el bytecode. Intente definir una función que haga e, f = 128, 128 y luego llame a dis.dis en ella, y verá que hay una sola valor constante (128, 128)

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Puede notar que el compilador ha almacenado 128 como una constante a pesar de que en realidad no es utilizada por el bytecode, lo que le da una idea de lo poca optimización que hace el compilador de CPython. Lo que significa que las tuplas (no vacías) en realidad no terminan fusionadas:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Puesto que en una función, dis, y mirar el co_consts-hay un 1 y a 2, dos (1, 2) tuplas que comparten el mismo 1 y 2, pero no son idénticas, y una tupla ((1, 2), (1, 2)) que tiene las dos tuplas iguales distintas.


Hay una optimización más que CPython hace: interning de cadenas. A diferencia del plegado constante del compilador, esto no está restringido a literales de código fuente:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

Por otro lado, se limita al tipo str, y a cadenas de de tipo de almacenamiento interno "ascii compact", "compact" o "legacy ready", y en muchos casos solo "ascii compact" se internará.


En todo caso, las reglas para lo que los valores deben ser, podrían ser, o no pueden ser distintos varían de implementación a implementación, y entre versiones de la misma implementación, y tal vez incluso entre ejecuciones del mismo código en la misma copia de la misma implementación.

Puede valer la pena aprender las reglas para un Python específico por diversión. Pero no vale la pena confiar en ellos en su código. La única regla segura es:

  • No escriba código que asuma dos iguales pero creados por separado inmutables los valores son idénticos.
  • No escriba código que asuma que dos valores inmutables iguales pero creados por separado son distintos.

O, en otras palabras, solo use is para probar los singletons documentados (como None) o que solo se crean en un lugar del código (como el modismo _sentinel = object()).

 2
Author: abarnert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-25 03:48:45