¿Cómo documentar los atributos de clase en Python? [cerrado]

115

Estoy escribiendo una clase ligera cuyos atributos están destinados a ser de acceso público y solo a veces se anulan en instancias específicas. No hay ninguna disposición en el lenguaje Python para crear cadenas de documentos para atributos de clase, o cualquier tipo de atributos, para el caso. ¿Cuál es la forma esperada y admitida, debería haber una, de documentar estos atributos? Actualmente estoy haciendo este tipo de cosas:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
    """

    flight_speed = 691
    __doc__ += """
        flight_speed (691)
          The maximum speed that such a bird can attain.
    """

    nesting_grounds = "Raymond Luxury-Yacht"
    __doc__ += """
        nesting_grounds ("Raymond Luxury-Yacht")
          The locale where these birds congregate to reproduce.
    """

    def __init__(self, **keyargs):
        """Initialize the Albatross from the keyword arguments."""
        self.__dict__.update(keyargs)

Esto dará como resultado que el docstring de la clase contenga la sección de docstring estándar inicial, así como las líneas agregadas para cada atributo mediante una asignación aumentada a __doc__.

Aunque este estilo no parece estar expresamente prohibido en las pautas de estilo de la cadena de documentos , tampoco se menciona como una opción. La ventaja aquí es que proporciona una forma de documentar atributos junto con sus definiciones, al mismo tiempo que crea una cadena de documentos de clase presentable y evita tener que escribir comentarios que reiteran la información de la cadena de documentos. Todavía estoy un poco molesto por tener que escribir los atributos dos veces; Estoy considerando usar las representaciones de cadena de los valores en la cadena de documentos para al menos evitar la duplicación de los valores predeterminados.

¿Es esta una violación atroz de las convenciones comunitarias ad hoc? ¿Está bien? ¿Existe una forma mejor? Por ejemplo, es posible crear un diccionario que contenga valores y cadenas de documentos para los atributos y luego agregar el contenido a la clase __dict__y la cadena de documentos hacia el final de la declaración de la clase; esto aliviaría la necesidad de escribir dos veces los nombres y valores de los atributos. editar : esta última idea, creo, no es realmente posible, al menos no sin construir dinámicamente toda la clase a partir de datos, lo que parece una muy mala idea a menos que haya alguna otra razón para hacerlo.

Soy bastante nuevo en Python y todavía estoy trabajando en los detalles del estilo de codificación, por lo que las críticas no relacionadas también son bienvenidas.

intuido
fuente
Si está buscando una forma de documentar los atributos del modelo de Django, esto podría ser útil: djangosnippets.org/snippets/2533
Michael Scheper
3
Duplicado de ¿Cómo documentar campos y propiedades en Python? que tienen una solución diferente.
bufh
1
No entiendo por qué esto se basa en opiniones. Python documenta específicamente sus convenciones aceptables en PEP. Existen diferentes herramientas fuente de Python que extraen documentación con el formato adecuado. De hecho, Python tiene un attribute doc stringmencionado en PEP 257 que no es muy conocido y parece difícil de encontrar que pueda responder a la pregunta de los OP, y es compatible con algunas herramientas de origen. Esta no es una opinión. Es un hecho, y parte del lenguaje, y casi exactamente lo que quiere el OP.
NeilG

Respuestas:

83

Para evitar confusiones: el término propiedad tiene un significado específico en Python. De lo que estás hablando es de lo que llamamos atributos de clase . Dado que siempre se actúa sobre ellos a través de su clase, encuentro que tiene sentido documentarlos dentro de la cadena de documentos de la clase. Algo como esto:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
        flight_speed     The maximum speed that such a bird can attain.
        nesting_grounds  The locale where these birds congregate to reproduce.
    """
    flight_speed = 691
    nesting_grounds = "Throatwarbler Man Grove"

Creo que es mucho más fácil para los ojos que el enfoque de tu ejemplo. Si realmente quisiera que apareciera una copia de los valores de los atributos en la cadena de documentos, los pondría al lado o debajo de la descripción de cada atributo.

Tenga en cuenta que en Python, las cadenas de documentos son miembros reales de los objetos que documentan, no simplemente anotaciones de código fuente. Dado que las variables de atributo de clase no son objetos en sí mismas, sino referencias a objetos, no tienen forma de contener cadenas de documentos propias. Supongo que podría defender las cadenas de documentos en las referencias, tal vez para describir "lo que debería ir aquí" en lugar de "lo que realmente está aquí", pero me resulta bastante fácil hacerlo en la cadena de documentos de la clase que las contiene.

ʇsәɹoɈ
fuente
Supongo que en la mayoría de los casos esto está bien, ya que los atributos —gracias por la corrección de la terminología— se declaran lo suficientemente sucintamente como para que puedan agruparse al principio de la declaración de clase sin que sea impráctico cambiar de un lado a otro para {leer ambos la documentación y el valor predeterminado} o {actualizar ambas instancias de la documentación y / o el valor predeterminado}.
intuido el
1
También tenga en cuenta que mi ejemplo hará que la documentación de los atributos aparezca en la cadena de documentos de la clase. De hecho, preferiría poner la documentación en cadenas de documentos de los atributos en sí, pero esto no funciona para la mayoría de los tipos integrados.
intuido
Sí, mi idea inicial era simplemente declarar, por ejemplo flight_speed = 691; flight_speed.__doc__ = "blah blah". Creo que esto es lo que mencionas en tu edición . Desafortunadamente, esto no funciona para instancias de (¿la mayoría?) Tipos integrados (como inten ese ejemplo). Funciona para instancias de tipos definidos por el usuario. =========== En realidad, hubo un PEP (lo siento, olvide el número) que propuso agregar cadenas de documentos para los atributos de clase / módulo, pero se rechazó porque no pudieron encontrar una manera de aclararlo si las cadenas de documentos eran para los atributos anteriores o siguientes.
intuido el
2
¿y si son atributos de instancia? todavía documento en la clase docstring o qué?
n611x007
1
@intuited ¿Fue este PEP? legacy.python.org/dev/peps/pep-0224
taz
30

Citas PEP257: Convenciones de cadenas de documentos, en la sección ¿Qué es una cadena de documentos? Se indica:

Los literales de cadena que aparecen en otras partes del código Python también pueden actuar como documentación. No son reconocidos por el compilador de código de bytes de Python y no son accesibles como atributos de objeto en tiempo de ejecución (es decir, no asignados a __doc__), pero las herramientas de software pueden extraer dos tipos de cadenas de documentos adicionales:

Los literales de cadena que ocurren inmediatamente después de una asignación simple en el nivel superior de un módulo, clase o método __init__ se denominan "cadenas de documentos de atributo".

Y esto se explica con más detalle en PEP 258: Cadenas de documentación de atributos. Como se explica arriba ʇsәɹoɈ. un atributo no es un objeto que pueda poseer un __doc__ por lo que no aparecerán en help()o pydoc. Estas cadenas de documentación solo se pueden utilizar para la documentación generada.

Se utilizan en Sphinx con la directiva autoattribute

Sphinx puede usar comentarios en una línea antes de una tarea o un comentario especial después de una tarea o una cadena de documentos después de la definición que se autodocumentará.

Marcz
fuente
1
El complemento jedi-vim también reconoce cadenas de documentos de atributos.
Long Vu
1
No sé cuándo se introdujo esto, pero Sphinx 1.2.2 parece incluir cadenas de documentación de atributos en la documentación generada.
jochen
1
Gracias @jochen, actualizo mi respuesta.
marcz
3
Tenga en cuenta que la PEP 258 se rechaza. El aviso de rechazo dice: "Si bien esto puede servir como un documento de diseño interesante para las ahora independientes docutils, ya no está programado para su inclusión en la biblioteca estándar".
Michał Łazowik
13

Podría abusar de las propiedades en este sentido. Las propiedades contienen un captador, un definidor, un eliminador y una cadena de documentos . Ingenuamente, esto se volvería muy detallado:

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """Docstring goes here."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Entonces tendrás una cadena de documentos que pertenece a Cx:

In [24]: print(C.x.__doc__)
Docstring goes here.

Hacer esto para muchos atributos es engorroso, pero podría imaginarse una función auxiliar myprop:

def myprop(x, doc):
    def getx(self):
        return getattr(self, '_' + x)

    def setx(self, val):
        setattr(self, '_' + x, val)

    def delx(self):
        delattr(self, '_' + x)

    return property(getx, setx, delx, doc)

class C:
    a = myprop("a", "Hi, I'm A!")
    b = myprop("b", "Hi, I'm B!")

In [44]: c = C()

In [46]: c.b = 42

In [47]: c.b
Out[47]: 42

In [49]: print(C.b.__doc__)
Hi, I'm B!

Entonces, llamar a Pythons interactivo helpdará:

Help on class C in module __main__:

class C
 |  Data descriptors defined here:
 |  
 |  a
 |      Hi, I'm A!
 |  
 |  b
 |      Hi, I'm B!

que creo que debería ser más o menos lo que buscas.

Editar : Ahora me doy cuenta de que quizás podamos evitar tener que pasar el primer argumento a myprop, porque el nombre interno no importa. Si las llamadas posteriores de myproppueden comunicarse de alguna manera entre sí, podría decidir automáticamente sobre un nombre de atributo interno largo e improbable. Estoy seguro de que hay formas de implementar esto, pero no estoy seguro de si valen la pena.

gerrit
fuente