3

¡Hola comunidad!

Recientemente realicé una publicación acerca de cómo crear variables en función a un valor ingresado. Recibí una solución donde se implementaba el método setattr() para lograr este objetivo.

Parte de la solución quedó establecida mediante las siguientes líneas de código:

class MisVectores:

    def cantidad(self, num):
        for n in range(1, num + 1):
            setattr(self, f"vector_{n}", 0)          


ejemplo = MisVectores()
ejemplo.cantidad(3)
print(ejemplo.vector_1)

Para comprender qué ocurría cuando se ejecutaba estuve cambiando algunas partes de la sintaxis y me topé con que se puede sustituir el caracter "f" en la línea donde se encuentra setattr(self, f"vector_{n}", 0), es decir, se pueden colocar los caracteres "r", "u" y "b". Ejemplo:

class MisVectores:

    def cantidad(self, num):
        for n in range(1, num + 1):
            setattr(self, r"vector_{n}", 0)  # Anteriormente, setattr(self, f"vector_{n}", 0)     

ejemplo = MisVectores()
ejemplo.cantidad(3)
print(ejemplo.vector_1)

Cuando este último se ejecutaba aparece el error:

AttributeError: 'MisVectores' object has no attribute 'vector_1'

La publicación original se encuentra en: ¿Cómo crear una cantidad de variables en función a un valor ingresado?

FJSevilla
  • 55,603
  • 7
  • 35
  • 58
gv-pro
  • 361
  • 1
  • 10
  • Hola gv-pro, he editado el título de la pregunta porque realmente setattr no desempeña un papel importante en el problema como explico en mi respuesta, realmente el problema es que hacen r"", f"" y b"". Creo con bastante seguridad que la pregunta será de utilidad a futuros usuarios y así será más fácilmente encontrada, Un Saludo. – FJSevilla May 20 '20 at 07:17
  • Hola FJSevilla! Me parece buena la modificación. Mientras más usuarios puedan guiarse de esta publicación será mejor. – gv-pro May 20 '20 at 18:53

2 Answers2

2

Los prefijos que se anteponen ante un literal determinan el tipo del literal y cómo el intérprete lo trata al generarlo.

Literales de cadena formateados

Se especifican anteponiendo una "f" de "format" delante del literal, f"", lo que indica que se trata de literales de cadena "formateados".

Se introdujeron por primera vez en Python 3.6 a partir de PEP 498 -- Literal String Interpolation y vienen a completar las otras formas ya existentes de formateo de cadenas que existen en Python:

  • El viejo estilo heredado de C usando %:

    >>> "%.2f %s %.2f = %.2f" % (7, "/", 3, 7/3)
    '7.00 / 3.00 =  2.33'
    
    >>> '%10s' % ('Hola',)
    '      Hola'
    
  • Método de las cadenas str.format() y str.format_map, builtin format().

    >>> "{:.2f} {} {:.2f} = {:.2f}".format(7, "/", 3, 7/3)
    '7.00 / 3.00 =  2.33'
    
    >>> '{:>10}'.format('Hola')
    '      Hola'
    
    >>> "{num1:.2f} {operador} {num2:.2f} = {resultado:.2f}".format(
           num1=7, operador="/", num2=3, resultado=7/3
           )
    '7.00 / 3.00 =  2.33'
    
    >>> "{num1:.2f} {operador} {num2:.2f} = {resultado:.2f}".format_map(
           {"num1": 7, "operador": "/", "num2": 3, "resultado": 7/3}
           )
    '7.00 / 3.00 =  2.33'
    
    >>> format(255, "02X")
    'FF'
    

Los f"" strings siguen la misma sintaxis que str.format:

En esencia las parejas de llaves {} delimitan una diana de reemplazo. El código encerrado en las llaves es evaluado como código Python al procesar el literal y el resultado de la evaluación se usa para reemplazar todo los que hay dentro de las llaves, llaves incluidas.

Cuando haces f"vector_{n}" el intérprete reemplaza {n} por el valor de la variable n en forma de cadena (str(n)). Por lo que:

>>> n = 1
>>> f"vector_{n}"
'vector_1'

La diferencia es que son literales de cadena. Son considerablemente más eficientes que el uso de str.format dado que se evita la generación de la cadena inicial primero y la llamada al método después, además de ser más legibles, por lo que deben usarse preferentemente. No obstante hay casos en los que la necesidad de dinamismo en tiempo de ejecución hace necesario usar str.format, por lo qu no remplazan ni mucho menos a éste.

Literales de cadena unicode

Se denotan con una "u" de "unicode" delante de la cadena.

Su utilidad actual es escasa dado que es un remanente de Python 2. En Python 2 las cadenas (str) son ASCII, por lo que se usaba u"" para generar literales de cadena Unicode. En Python 3 las cadenas usan todas "UTF-8" por defecto, por lo que el uso de u"" es redundante. De hecho, se eliminó de Python 3, pero posteriormente (a partir de Python 3.3) fue repuesto para hacer el código compatible con Python 2, por entonces mayoritario:

Su uso en Python 3 es incorrecto si no es por razones de retrocompatibilidad con el oficialmente extinto Python 2.

Python 2
>>> cad = "cigüeña"
>>> u_cad = u"cigüeña"
>>> cad == u_cad
__main__:1: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
False
>>> type(cad)
<type 'str'>
>>> type(u_cad)
<type 'unicode'>
Python 3
>>> cad = "cigüeña"
>>> u_cad = "cigüeña"
>>> cad == u_cad
True
>>> type(u_cad)
<class 'str'>
>>> type(cad)
<class 'str'>

Literales de cadena "crudos"

Se denotan con una "r" de "raw" delante del literal, r"".

Básicamente lo que se encierra entre comillas, todo, es interpretado como un carácter literal. Es por tanto importante en el caso de \, carácter que denota secuencias de escape,como \t (tabulación), \n nueva línea, \r (retorno de carro), etc:

>>> print("Hola\n¿qué\ttal?")
Hola
¿qué    tal?

>>> print(r"Hola\n¿qué\ttal?")
Hola\n¿qué\ttal?

Son útiles por tanto cuando necesitamos una cadena en la que todo sea interpretado como un carácter literal sin interpretar las secuencias de escape. Por ejemplo, a la hora de crear expresiones regulares.

En tu caso, cuando haces r"vector_{n}" el atributo se crea con el nombre "vector_{n}" literalmente, no con "vector_1":

>>> n = 1
>>> r"vector_{n}"
'vector_{n}'

Por eso luego te salta que el atributo vector_1 no existe, lo que se ha creado es una clave en el diccionario con valor 'vector_{n}', el cual por cierto es un nombre no válido para un atributo por los {}.

Literales para cadenas de bytes

Por último, anteponiendo una "b" o "B" antes de un literal, b""/B"", creamos una instancia del tipo bytes en lugar de str. Solo puede contener caracteres ASCII y los bytes con valor numérico mayor a 127 tienen que representarse mediante secuencias de escape.

>>> b_cad = b"Eso vale 13\xe2\x82\xac"
>>> b_cad.decode("UTF-8")
'Eso vale 13€'

Son similares por tanto al antiguo str de Python 2, de hecho en Python 2 son ignorados y son redundantes, igual que pasaba con u"" en Python 3.


Por lo tanto, cada prefijo crea cosas diferentes, no puedes cambiar f"" por r"" o b"". En cambio, lo siguiente si es equivalente:

f"vector_{n}" 
"vector_{}".format(n)
"vector_{numero}".format(numero=n)
"vector_{numero}".format_map({numero: n})
"vector_%d" % (n, )
"vector_" + str(n)
 ...
FJSevilla
  • 55,603
  • 7
  • 35
  • 58
0

Mas allá de la explicación del funcionamiento "setattr", en este caso el problema se encuentra en uso del carácter para devolver el string. Al poner r' todo lo que sigue a continuación, se interpreta como string. En cambio, al poner f', además de interpretarse como string, se pueden leer variables (es lo que se coloca entre llaves {}). Entonces, escrito de esa manera, el atributo que estas definiendo, queda literalmente como "vector_{n}"; sin importar el valor de "n".

palabra = 'Juan'
print(r'Hola que {palabra}, como estas')
print(f'Hola que {palabra}, como estas')

Prueba el código de arriba para ver la diferencia.