Che cosa è `1..__truediv__` ? Non Python dispone di un .. (“dot dot”) notazione sintassi?

Recentemente mi sono imbattuto in una sintassi non ho mai visto prima quando ho imparato python, né nella maggior parte dei tutorial, .. notazione, sembra qualcosa di simile a questo:

f = 1..__truediv__ # or 1..__div__ for python 2

print(f(8)) # prints 0.125 

Ho pensato che era esattamente lo stesso (tranne che è più, ovviamente):

f = lambda x: (1).__truediv__(x)
print(f(8)) # prints 0.125 or 1//8

Ma le mie domande sono:

  • Come può fare?
  • Che cosa significa in concreto con i due punti?
  • Come si può utilizzare in una istruzione più complessa (se possibile)?

Questo probabilmente mi salva molte righe di codice in futuro…:)

  • Nota: (1).__truediv__ non è proprio la stessa 1..__truediv__, come l’ex chiamate int.__truediv__ mentre le seconde non float.__truediv__. In alternativa, è anche possibile utilizzare 1 .__truediv__ (con uno spazio)`
  • Nota che 1//8 è 0, non 0.125, in entrambe le versioni di Python.
  • mi ricorda if (x <- 3) {...}
  • Qui è un esempio di questo utilizzo.
  • Se avete intenzione di scrivere una lambda, scrivere lambda x: 1/x, che è un carattere in meno (la stessa lunghezza, se avete bisogno di 1./x) tutto ciò che comporta una chiamata esplicita a un __method__ (al di fuori di una classe derivata che l’override di un metodo) probabilmente rientra nella categoria “stupido python trucchi” – divertente ragione, ma quasi certamente non appartengono nel codice di produzione.
  • Per favore puoi dare una spiegazione con il tuo commento così le ragioni per cui questo dovrebbe essere fatto sono comprensibili?
  • L’alta qualità delle risposte e commenti mostrare il codice di esempio esigenze intuizione di capire, è stata una sorpresa per molti, non ha alternative che sono più chiari, più generale, e almeno altrettanto efficiente. La mia lamentela principale è che la leggibilità conta. Salvare bravura per dove è più necessario, comunicare con gli esseri umani.

InformationsquelleAutor abccd | 2017-04-19

 

4 Replies
  1. 212

    Quello che hai è un float letterale senza lo zero finale, cui si accede quindi __truediv__ metodo. Non è un operatore in sé; il primo punto è una parte del valore di float, e il secondo è il dot operatore di accedere alle proprietà di oggetti e metodi.

    Si può raggiungere lo stesso punto, eseguendo le seguenti operazioni.

    >>> f = 1.
    >>> f
    1.0
    >>> f.__floordiv__
    <method-wrapper '__floordiv__' of float object at 0x7f9fb4dc1a20>

    Un altro esempio

    >>> 1..__add__(2.)
    3.0

    Qui si aggiunge 1.0 a 2.0, il che comporta ovviamente 3.0.

    • Così che cosa abbiamo trovato è un dev che hanno sacrificato un sacco di chiarezza per un po ‘ di brevità e qui ci sono.
    • Forse che qualcuno è salvare il suo codice sorgente a 5,5″ floppy disk?
    • sarebbe da 5,25″ iirc 😉
    • Il mio male, io non ero ancora nato :p
    • Non capisco come questo è salvare la brevità. Non è 1.0+2 più corta?
    • Egli può trovare in questo codice recente golf presentazione.
    • infatti:) e ‘ stato divertente vederli golf
    • Fatto divertente, è anche possibile fare questo in JavaScript: 1..toString()
    • Ops, ero confusa 1+2 e .1+.2. Il mio male.

  2. 73

    La domanda è già sufficientemente risposto (cioè @Paolo Rooneyrisposta), ma è anche possibile verificare la correttezza di queste risposte.

    Fatemi chiudere esistenti risposte: La .. non è un singolo sintassi elemento!

    È possibile controllare come il codice sorgente è “token”. Questi simboli rappresentano il modo in cui il codice viene interpretato:

    >>> from tokenize import tokenize
    >>> from io import BytesIO
    
    >>> s = "1..__truediv__"
    >>> list(tokenize(BytesIO(s.encode('utf-8')).readline))
    [...
     TokenInfo(type=2 (NUMBER), string='1.', start=(1, 0), end=(1, 2), line='1..__truediv__'),
     TokenInfo(type=53 (OP), string='.', start=(1, 2), end=(1, 3), line='1..__truediv__'),
     TokenInfo(type=1 (NAME), string='__truediv__', start=(1, 3), end=(1, 14), line='1..__truediv__'),
     ...]

    Quindi la stringa 1. è interpretata come un numero, il secondo . è un OP (un operatore, in questo caso il “get attributo di” operatore) e la __truediv__ è il nome del metodo. Quindi questo è solo l’accesso alla __truediv__ metodo del galleggiante 1.0.

    Un altro modo di vedere il bytecode generato è disassemblare di esso. Questo, in realtà, mostra le istruzioni che vengono eseguite quando il codice viene eseguito:

    >>> import dis
    
    >>> def f():
    ...     return 1..__truediv__
    
    >>> dis.dis(f)
      4           0 LOAD_CONST               1 (1.0)
                  3 LOAD_ATTR                0 (__truediv__)
                  6 RETURN_VALUE

    Che sostanzialmente dice le stesse. Carica l’attributo __truediv__ della costante 1.0.


    Per quanto riguarda la tua domanda

    E come è possibile utilizzare in un modo più complesso di istruzione (se possibile)?

    Anche se è possibile si dovrebbe mai scrivere codice come quello, semplicemente perché non è chiaro quale sia il codice facendo. Quindi, si prega di non utilizzare in dichiarazioni più complesse. Vorrei anche andare così lontano che non si deve usare in modo “semplice” dichiarazioni, almeno si dovrebbe utilizzare parentesi per separare le istruzioni:

    f = (1.).__truediv__

    questo sarebbe sicuramente più leggibile ma qualcosa lungo le linee di:

    from functools import partial
    from operator import truediv
    f = partial(truediv, 1.0)

    sarebbe ancora meglio!

    L’approccio che utilizza partial conserva anche python modello di dati (il 1..__truediv__ approccio non!) che può essere dimostrato da questo piccolo snippet:

    >>> f1 = 1..__truediv__
    >>> f2 = partial(truediv, 1.)
    
    >>> f2(1+2j)  # reciprocal of complex number - works
    (0.2-0.4j)
    >>> f2('a')   # reciprocal of string should raise an exception
    TypeError: unsupported operand type(s) for /: 'float' and 'str'
    
    >>> f1(1+2j)  # reciprocal of complex number - works but gives an unexpected result
    NotImplemented
    >>> f1('a')   # reciprocal of string should raise an exception but it doesn't
    NotImplemented

    Questo perché 1. /(1+2j) non è valutata da float.__truediv__ ma con complex.__rtruediv__operator.truediv assicura l’operazione inversa viene chiamato quando il normale funzionamento restituisce NotImplemented ma non hai queste fallbacks quando si opera sul __truediv__ direttamente. Questa perdita di “comportamenti attesi” è il motivo principale per cui è (normalmente) non dovrebbero utilizzare metodi magici direttamente.

  3. 41

    Due punti insieme può essere un po ‘ scomodo in un primo momento:

    f = 1..__truediv__ # or 1..__div__ for python 2

    Ma è come scrivere:

    f = 1.0.__truediv__ # or 1.0.__div__ for python 2

    Perché float valori letterali possono essere scritti in tre forme:

    normal_float = 1.0
    short_float = 1.  # == 1.0
    prefixed_float = .1  # == 0.1
    • Questo è sorprendente, perché sono questi sintassi valida ma 1.__truediv__ non è?
    • Vedere qui. Il . sembra essere analizzati come parte del numero, e quindi la . per il metodo della funzione di accesso è mancante.
    • Ma dal momento che è scomodo e poco chiaro sintassi, probabilmente dovrebbe essere evitato.
  4. 11

    Cosa è f = 1..__truediv__?

    f è un limite metodo speciale su un carro con un valore di uno. In particolare,

    1.0 / x

    in Python 3, invoca:

    (1.0).__truediv__(x)

    Prove:

    class Float(float):
        def __truediv__(self, other):
            print('__truediv__ called')
            return super(Float, self).__truediv__(other)

    e:

    >>> one = Float(1)
    >>> one/2
    __truediv__ called
    0.5

    Se facciamo:

    f = one.__truediv__

    Conserviamo un nome associato a quel metodo vincolato

    >>> f(2)
    __truediv__ called
    0.5
    >>> f(3)
    __truediv__ called
    0.3333333333333333

    Se stavamo facendo che punteggiato di ricerca in un loop stretto, questo potrebbe risparmiare un po ‘ di tempo.

    L’analisi dell’Albero di Sintassi Astratta (AST)

    Possiamo vedere che l’analisi dell’AST per l’espressione ci dice che stiamo ottenendo i __truediv__ attributo sul numero in virgola mobile, 1.0:

    >>> import ast
    >>> ast.dump(ast.parse('1..__truediv__').body[0])
    "Expr(value=Attribute(value=Num(n=1.0), attr='__truediv__', ctx=Load()))"

    Si potrebbe ottenere la stessa funzione risultante da:

    f = float(1).__truediv__

    O

    f = (1.0).__truediv__

    Detrazione

    Si può anche arrivare per deduzione.

    Andiamo a costruire.

    1 di per sé è un int:

    >>> 1
    1
    >>> type(1)
    <type 'int'>

    1 con un punto dopo è un float:

    >>> 1.
    1.0
    >>> type(1.)
    <type 'float'>

    Successivo punto per sé sarebbe un SyntaxError, ma si comincia a punti di ricerca sull’istanza del galleggiante:

    >>> 1..__truediv__
    <method-wrapper '__truediv__' of float object at 0x0D1C7BF0>

    Nessuno ha parlato di questo – Questo è ora una “metodo vincolato” sul galleggiante, 1.0:

    >>> f = 1..__truediv__
    >>> f
    <method-wrapper '__truediv__' of float object at 0x127F3CD8>
    >>> f(2)
    0.5
    >>> f(3)
    0.33333333333333331

    Si può eseguire la stessa funzione molto più readably:

    >>> def divide_one_by(x):
    ...     return 1.0/x
    ...     
    >>> divide_one_by(2)
    0.5
    >>> divide_one_by(3)
    0.33333333333333331

    Prestazioni

    Il rovescio della divide_one_by funzione che richiede un altro Python stack frame, il che rende un po ‘ più lento del metodo vincolato:

    >>> def f_1():
    ...     for x in range(1, 11):
    ...         f(x)
    ...         
    >>> def f_2():
    ...     for x in range(1, 11):
    ...         divide_one_by(x)
    ...         
    >>> timeit.repeat(f_1)
    [2.5495760687176485, 2.5585621018805469, 2.5411816588331888]
    >>> timeit.repeat(f_2)
    [3.479687248616699, 3.46196088706062, 3.473726342237768]

    Naturalmente, se si può semplicemente utilizzare il semplice letterali, che è anche più veloce:

    >>> def f_3():
    ...     for x in range(1, 11):
    ...         1.0/x
    ...         
    >>> timeit.repeat(f_3)
    [2.1224895628296281, 2.1219930218637728, 2.1280188256941983]

Lascia un commento