Por poder, puedes usar setattr
y un simple ciclo for
:
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)
Ahora bien, personalmente ante este tipo de problemas, tu clase parece actuar como un contenedor/administrador de un conjunto homogéneo de objetos, prefiero no añadir atributos en tiempo de ejecución sino usar un contenedor. Puedes usar un diccionario. Si quieres acceder a los objetos como un atributo de la instancia simplemente define el método __getattr__
(y/o reimplementa __setattr__
):
class MisVectores():
def __init__(self):
self._vectores = {}
def agregar_vectores(self, num):
for n in range(len(self._vectores) + 1, num + len(self._vectores) + 1):
self._vectores[f"vector_{n}"] = 0
def __getattr__(self, name):
if (attr:= self._vectores.get(name)) is not None:
return attr
raise AttributeError(
f"'{self.__class__.__name__}' object has no attribute '{name}'"
)
def __setattr__(self, name, value):
spl = name.split("_")
if len(spl) == 2 and spl[0] == "vector" and spl[1].isdigit():
vectores = self._vectores[name] = value
return
super().__setattr__(name, value)
ejemplo = MisVectores()
ejemplo.agregar_vectores(3)
print(ejemplo.vector_2)
ejemplo.agregar_vectores(3)
print(ejemplo.vector_6)
ejemplo.vector_6 = 5
print(ejemplo.vector_6)
Sin duda es más complicado, pero tiene sus ventajas. Ahora tienes todos los objetos "vector" almacenados en un diccionario que es atributo de instancia, están separados y aislados del resto de atributos/métodos de la clase facilitando enormemente su manejo, eliminación o adición por parte de la clase y cualquiera de sus métodos. Además el acceso a ellos mediante "dot notation" (instancia.atributo
) no difiere.
Explicación de algunos conceptos planteados en comentarios:
El uso de _
(un solo guion) antes del nombre de un atributo (self._vectores
), variable, método o función es solo una convención que indica que es "privado". No tiene significado alguno para el lenguaje (los atributos/métodos privados no existen en Python), solo es una señal para otro desarrollador que básicamente dice "ésto debes considerarlo de uso privado e interno de la clase, no debe ser accedido o modificado directamente, si lo haces se supone que sabes lo que haces, luego no vengas llorando si todo explota... XD".
La sentencia attr:= self._vectores.get(name))
no tiene mucho de especial, self._vectores
es un diccionario, get
es el método dict.get
que obtiene el valor asociado de una clave (name
) si existe o retorna un valor por defecto si no lo hace (por defecto None
).
attr:=
es una expresión de asignación o operador "walrus" (morsa por la forma :=
) introducidas en Python 3.8. Simplemente permie asignar un valor a una variable en la misma sentencia condicional que la evalúa, es equivalente en éste caso a:
attr = self._vectores.get(name)
if attr is not None:
# ...
Lo que hacemos es ver si existe la clave name
en el diccionario, si existe la retornamos como si fuera un atributo y si no lanzamos una excepción.
La sentencia super().__getattribute__("_vectores")[name] = value
lo que hace es invocar al método __getattribute__
del padre de la clase (super
) y delegar en en el la resolución del atributo self._vectores
.
El método __getattribute__
es el primer método encargado de la resolución de atributos, cuando hacemos instancia.método/instancia.attributo
el primero en invocarse es él, exista o no dicho método/atributo. Toda clase tiene dicho método implementado y es heredadado de object
.
Por lo tanto, super().__getattribute__("_vectores") obtiene una referencia a
self._vectoresy
[name] = value` crea una nueva clave y le asigna su valor en dicho diccionario.
Realmente en éste caso no es necesario delegar en el padre, no con la implementación actual, basta con vectores = self._vectores[name] = value
. Inicialmente no incluí el condicional previo y en ese caso era necesario para evitar la recursión infinita. He modificado el código arriba también, pero dejo de todas forma la explicación aquí.
El método __getattr__
es invocado en última instancia si la obtención del atributo siguiendo toda la cadena de resolución ha fallado, básicamente si el atributo no existe ni en la clase ni heredado, ni es una property
, ni un método, etc. No está implementado por defecto y hay que tener en cuenta que se llama siempre el último, __getattibute__
es llamado antes siempre.
Por último super().__setattr__(name, value)
de nuevo delega en el modo __setattr__
del padre (object
en éste caso). El método __setattr__
es el hermano de __getattribute__
, es invocado siempre que se realiza una asignación a un atributo instancia.atributo = valor
. Como reimplementamos el método en la clase hija para poder asignar valores a los "atributos" del diccionario mediante instancia.vector_n = valor
, si el atributo no es uno de los que vive en self._diccionario
debemos delegar en el método del padre, para el resto de atributos.
Es un tema complejo que implica a toda la implementación de la POO en Python y que da no solo para otra publicación, sino para varias. Algunos preguntas relacionadas que pueden ayudar: