¿Es una mejor práctica preinicializar atributos en una clase o agregarlos en el camino?

12

Lamento si esta es una pregunta ABSOLUTAMENTE de segundo año, pero tengo curiosidad por saber cuáles son las mejores prácticas, y parece que no puedo encontrar una buena respuesta en Google.

En Python, usualmente uso una clase vacía como un contenedor de estructura de datos super-catchall (algo así como un archivo JSON), y agrego atributos en el camino:

class DataObj:
    "Catch-all data object"
    def __init__(self):
        pass

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Esto me da una gran cantidad de flexibilidad, porque el objeto contenedor esencialmente puede almacenar cualquier cosa. Entonces, si surgen nuevos requisitos, lo agregaré como otro atributo al objeto DataObj (que paso en mi código).

Sin embargo, recientemente me han impresionado (los programadores de FP) que esta es una práctica horrible, porque hace que sea muy difícil leer el código. Uno tiene que revisar todo el código para descubrir qué atributos tiene realmente DataObj.

Pregunta : ¿Cómo puedo reescribir esto para una mayor capacidad de mantenimiento sin sacrificar la flexibilidad?

¿Hay alguna idea de la programación funcional que pueda adoptar?

Estoy buscando las mejores prácticas por ahí.

Nota : una idea es preinicializar la clase con todos los atributos que uno espera encontrar, por ej.

class DataObj:
    "Catch-all data object"
    def __init__(self):
        data.a = 0
        data.b = ""
        data.c = []

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

¿Es esta realmente una buena idea? ¿Qué pasa si no sé cuáles son mis atributos a priori?

Galaad
fuente
Sus estructuras de datos son tan mutables que parece estar preocupado por su mantenibilidad. En su abundante tiempo libre ™, intente leer este artículo sobre modelos de datos inmutables . Podría cambiar por completo la forma en que razona sobre los datos.
9000
@ 9000 Un artículo como ese vuelve a convencer a los ya convencidos. Para mí, parecía más un tutorial que un por qué (la lista de por qué no es realmente convincente a menos que sienta que tiene esas necesidades específicas). Para mí, no convence a alguien que actualiza las facturas en VB que tener que hacer constantemente nuevas copias de su objeto de factura tiene sentido (agregar pago, nuevo objeto de factura; agregar parte, nuevo objeto de factura).
Paul

Respuestas:

10

¿Cómo puedo reescribir esto para una mayor capacidad de mantenimiento sin sacrificar la flexibilidad?

Usted no La flexibilidad es precisamente lo que causa el problema. Si algún código en cualquier lugar puede cambiar los atributos que tiene un objeto, la mantenibilidad ya está hecha pedazos. Idealmente, cada clase tiene un conjunto de atributos que se establece en piedra después __init__y lo mismo para cada instancia. No siempre es posible o razonable, pero debería ser el caso siempre que no tenga buenas razones para evitarlo.

Una idea es preinicializar la clase con todos los atributos que uno espera encontrar

Esa no es una buena idea. Claro, entonces el atributo está allí, pero puede tener un valor falso, o incluso uno válido que cubra el código que no asigna el valor (o uno mal escrito). AttributeErrorda miedo, pero obtener resultados incorrectos es peor. Los valores predeterminados en general están bien, pero para elegir un valor predeterminado razonable (y decidir qué se requiere) necesita saber para qué se utiliza el objeto.

¿Qué pasa si no sé cuáles son mis atributos a priori?

Entonces, en cualquier caso, está jodido y debe usar un dict o una lista en lugar de codificar nombres de atributos. Pero supongo que querías decir "... en el momento en que escribo la clase contenedor". Entonces la respuesta es: "Puede editar archivos en bloque, duh". ¿Necesita un nuevo atributo? Agregue un atributo frigging a la clase contenedor. ¿Hay más código usando esa clase y no necesita ese atributo? Considere dividir las cosas en dos clases separadas (use mixins para mantenerse SECO), así que hágalo opcional si tiene sentido.

Si tiene miedo de escribir clases de contenedor repetitivas: aplique la metaprogramación juiciosamente o úsela collections.namedtuplesi no necesita mutar a los miembros después de la creación (sus amigos FP estarán encantados).


fuente
7

Siempre puedes usar la clase Bunch de Alex Martelli . En tu caso:

class DataObj:
    "Catch-all data object"
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

def processData(inputs):
    data = DataObj(a=1, b="sym", c=[2,5,2,1])

De esa manera, al menos está claro para el lector que los datos son solo un almacén de datos tonto y uno puede ver de inmediato qué valores se almacenan bajo qué nombre, ya que todo sucede en una línea.

Y sí, hacer las cosas de esta manera es, de hecho, una buena idea, a veces.

Pillmuncher
fuente
1

Probablemente usaría el segundo enfoque, posiblemente Nonepara indicar datos no válidos. Es cierto que es difícil de leer / mantener si agrega atributos más tarde. Sin embargo, más información sobre el propósito de esta clase / objeto daría una idea de por qué la primera idea es un mal diseño: ¿ dónde tendría una clase completamente vacía sin métodos o datos predeterminados? ¿Por qué no sabrías qué atributos tiene la clase?

Es posible que processDatasea ​​mejor como método ( process_datapara seguir las convenciones de nomenclatura de Python), ya que actúa sobre la clase. Dado el ejemplo, parece que podría ser mejor como estructura de datos (donde dictpuede ser suficiente).

Dado un ejemplo real, podría considerar llevar la pregunta a CodeReview , donde podrían ayudar a refactorizar el código.

Casey Kuball
fuente