Translate

sábado, 3 de febrero de 2024

Printing en Go


La impresión formateada en Go utiliza un estilo similar a la familia printf de C, pero es más rica y más general. Las funciones se encuentran en el paquete fmt y tienen nombres en mayúscula: fmt.Printf, fmt.Fprintf, fmt.Sprintf, etc. Las funciones de cadena (Sprintf, etc.) devuelven una cadena en lugar de enviar la cadena a un buffer determinado.

No es necesario proporcionar un formato. Para cada uno de Printf, Fprintf y Sprintf hay otro par de funciones, por ejemplo Print y Println. Estas funciones no toman una cadena de formato sino que generan un formato predeterminado para cada argumento. Las versiones Println también insertan un espacio en blanco entre los argumentos y agregan una nueva línea a la salida, mientras que las versiones Print agregan espacios en blanco solo si el operando en ninguno de los lados es una cadena. En este ejemplo, cada línea produce el mismo resultado.


fmt.Printf("Hello %d\n", 23)

fmt.Fprint(os.Stdout, "Hello ", 23, "\n")

fmt.Println("Hello", 23)

fmt.Println(fmt.Sprint("Hello ", 23))


Las funciones de impresión formateadas fmt.Fprint y las demás toman como primer argumento cualquier objeto que implemente la interfaz io.Writer; por ejemplo os.Stdout y os.Stderr.

Aquí las cosas empiezan a diferir de C. Primero, los formatos numéricos como %d no aceptan indicadores de signo o tamaño; en cambio, las rutinas de impresión utilizan el tipo de argumento para decidir estas propiedades.


var x uint64 = 1<<64 - 1

fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))


Esto imprime: 


18446744073709551615 ffffffffffffffff; -1 -1


Si solo desea la conversión predeterminada, como decimal para números enteros, puede usar el formato general %v (para “valor”); el resultado es exactamente lo que producirían Print y Println. Además, ese formato puede imprimir cualquier valor, incluso matrices, sectores, estructuras y mapas. Aquí hay una declaración impresa para el mapa de zona horaria.


fmt.Printf("%v\n", timeZone)  // or just fmt.Println(timeZone)


lo que da salida:


map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]


Para los mapas, Printf y las demás clasifican la salida lexicográficamente por clave.


Al imprimir una estructura, el formato modificado %+v anota los campos de la estructura con sus nombres y, para cualquier valor, el formato alternativo %#v imprime el valor en la sintaxis Go completa.


type T struct {

    a int

    b float64

    c string

}

t := &T{ 7, -2.35, "abc\tdef" }

fmt.Printf("%v\n", t)

fmt.Printf("%+v\n", t)

fmt.Printf("%#v\n", t)

fmt.Printf("%#v\n", timeZone)


imprime : 



&{7 -2.35 abc   def}

&{a:7 b:-2.35 c:abc     def}

&main.T{a:7, b:-2.35, c:"abc\tdef"}

map[string]int{"CST":-21600, "EST":-18000, "MST":-25200, "PST":-28800, "UTC":0}


(Tenga en cuenta los símbolos). Ese formato de cadena entre comillas también está disponible a través de %q cuando se aplica a un valor de tipo cadena o []byte. El formato alternativo %#q utilizará comillas inversas si es posible. (El formato %q también se aplica a números enteros y runas, lo que produce una constante rúnica entre comillas simples). Además, %x funciona en cadenas, matrices de bytes y porciones de bytes, así como en números enteros, generando una cadena hexadecimal larga y con un espacio. en el formato (% x) pone espacios entre los bytes.


Otro formato útil es %T, que imprime el tipo de un valor.


fmt.Printf("%T\n", timeZone)


imprime: 


map[string]int


Si desea controlar el formato predeterminado para un tipo personalizado, todo lo que se requiere es definir un método con la cadena de firma String() en el tipo. Para nuestro tipo T simple, podría verse así.


func (t *T) String() string {

    return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)

}

fmt.Printf("%v\n", t)


para imprimir en el formato


7/-2.35/"abc\tdef"


(Si necesita imprimir valores de tipo T así como punteros a T, el receptor de String debe ser de tipo valor; este ejemplo usó un puntero porque es más eficiente e idiomático para tipos de estructuras).

Nuestro método String puede llamar a Sprintf porque las rutinas de impresión son completamente reentrantes y se pueden empaquetar de esta manera. Sin embargo, hay un detalle importante que debe comprender acerca de este enfoque: no construya un método String llamando a Sprintf de una manera que se repetirá en su método String indefinidamente. Esto puede suceder si la llamada de Sprintf intenta imprimir el receptor directamente como una cadena, lo que a su vez invocará el método nuevamente. Es un error común y fácil de cometer, como muestra este ejemplo.


type MyString string


func (m MyString) String() string {

    return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.

}


También es fácil de solucionar: convierta el argumento al tipo de cadena básica, que no tiene el método.


type MyString string

func (m MyString) String() string {

    return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.

}


Otra técnica de impresión consiste en pasar los argumentos de una rutina de impresión directamente a otra rutina similar. La firma de Printf usa el tipo ...interfaz{} como argumento final para especificar que un número arbitrario de parámetros (de tipo arbitrario) puede aparecer después del formato.


func Printf(format string, v ...interface{}) (n int, err error) {


Dentro de la función Printf, v actúa como una variable de tipo []interfaz{} pero si se pasa a otra función variable, actúa como una lista normal de argumentos. Aquí está la implementación de la función log.Println que usamos anteriormente. Pasa sus argumentos directamente a fmt.Sprintln para el formato real.


// Println prints to the standard logger in the manner of fmt.Println.

func Println(v ...interface{}) {

    std.Output(2, fmt.Sprintln(v...))  // Output takes parameters (int, string)

}


Escribimos ... después de v en la llamada anidada a Sprintln para decirle al compilador que trate a v como una lista de argumentos; de lo contrario, simplemente pasaría v como argumento de un solo segmento.