¿Cómo romper una línea de métodos encadenados en Python?

138

Tengo una línea del siguiente código (no culpe a las convenciones de nombres, no son mías):

subkeyword = Session.query(
    Subkeyword.subkeyword_id, Subkeyword.subkeyword_word
).filter_by(
    subkeyword_company_id=self.e_company_id
).filter_by(
    subkeyword_word=subkeyword_word
).filter_by(
    subkeyword_active=True
).one()

No me gusta cómo se ve (no demasiado legible) pero no tengo una mejor idea para limitar las líneas a 79 caracteres en esta situación. ¿Hay una mejor manera de romperlo (preferiblemente sin barras invertidas)?

Juliusz Gonera
fuente

Respuestas:

256

Podrías usar paréntesis adicionales:

subkeyword = (
        Session.query(Subkeyword.subkeyword_id, Subkeyword.subkeyword_word)
        .filter_by(subkeyword_company_id=self.e_company_id)
        .filter_by(subkeyword_word=subkeyword_word)
        .filter_by(subkeyword_active=True)
        .one()
    )
algo
fuente
También me gusta más. No agrega más código y no tiene barras invertidas.
Juliusz Gonera
22
No estoy seguro de qué justifica la sangría adicional aquí; Creo que esta solución se lee igual de bien con las líneas colgantes sangradas solo una vez y el par final no aparece en absoluto.
Carl Meyer
44
En mi opinión, la doble sangría es útil aquí porque es visualmente distinta de un bloque sangrado normal. Cuando está rodeado por otro código, esto hace que sea más obvio que se trata de una sola línea envuelta.
algo del
1
La mejor respuesta, en términos de uso de parens. Como se menciona en un comentario de Shanimal en otra respuesta, el uso de la continuación de línea implícita a través de paréntesis es en realidad PEP 8 preferido frente al carácter de continuación ``
kevlarr
Prefiero barras invertidas. El paréntesis no es una pista para toda situación. Como ejemplo, no funciona con el operador de asignación. Imagínese usted quiere romper líneas en esta cadena:foo.set_default('bar', {}).set_default('spam', {}).set_default('eggs', {})['lol'] = 'yeah'
loutre
56

Este es un caso en el que se prefiere un carácter de continuación de línea para abrir paréntesis. La necesidad de este estilo se hace más evidente a medida que los nombres de los métodos se alargan y los métodos comienzan a tomar argumentos:

subkeyword = Session.query(Subkeyword.subkeyword_id, Subkeyword.subkeyword_word) \
                    .filter_by(subkeyword_company_id=self.e_company_id)          \
                    .filter_by(subkeyword_word=subkeyword_word)                  \
                    .filter_by(subkeyword_active=True)                           \
                    .one()

PEP 8 pretende ser interpretado con una medida de sentido común y un ojo para lo práctico y lo bello. Infringe felizmente cualquier directriz PEP 8 que resulte en un código feo o difícil de leer.

Dicho esto, si con frecuencia te encuentras en desacuerdo con PEP 8, puede ser una señal de que hay problemas de legibilidad que trascienden tu elección de espacios en blanco :-)

Raymond Hettinger
fuente
2
+1 en barras invertidas y alineando los filtros encadenados en este caso particular. Esta situación también surge en Django y es más legible de esta manera, pero en cualquier otra situación siento que las frases entre paréntesis son superiores (no sufra el problema "¿hay espacios en blanco después de mi barra diagonal inversa?"). Dicho esto, el uso de paréntesis de la frase se puede usar para lograr el mismo efecto, pero lo pone en modo de lectura Lisp en medio de la lectura de Python, lo que me parece discordante.
zxq9
11
No veo cómo esta solución es más capaz de hacer frente "a medida que los nombres de los métodos se alargan y los métodos comienzan a tomar argumentos" que "envolver en paréntesis externos" o "salto de línea después de cada par abierto y antes de cada par cerrado" soluciones De hecho, es peor manejar eso, ya que (al menos como se muestra aquí) requiere una sangría mucho más profunda para cada línea colgante.
Carl Meyer
1
Demasiada sangría para las llamadas de filtro. Una pestaña o 4 espacios habrían sido suficientes aquí. También alineación de `` ... ¿Cuántos segundos presionaste esa tecla de espacio? En general, estoy en contra de todas las formas, que requieren que golpee esa tecla de espacio como si no hubiera un mañana.
Zelphir Kaltstahl el
2
fwiw, PEP8 dice "La forma preferida de ajustar las líneas largas es mediante el uso de la continuación de línea implícita de Python dentro de paréntesis, corchetes y llaves. Las líneas largas se pueden dividir en varias líneas ajustando las expresiones entre paréntesis. Estas deben usarse con preferencia a la barra invertida para continuación de línea ". - Python.org Continúa discutiendo cuándo las barras invertidas pueden ser apropiadas
Shanimal
Gran referencia a PEP8! Un problema molesto aquí con la alineación de todas las .filterllamadas es que si cambia subkeyworda sub_keyword, ahora tiene que corregir la sangría de cada línea solo porque cambió el nombre de la variable. No es un buen estilo, cuando en realidad obstaculiza mantenimiento ...
kevlarr
15

Mi elección personal sería:

subkeyword = Session.query (
    Subkeyword.subkeyword_id,
    Subkeyword.subkeyword_word,
).filtrado por(
    subkeyword_company_id = self.e_company_id,
    subkeyword_word = subkeyword_word,
    subkeyword_active = True,
).uno()
pkoch
fuente
1
Estoy de acuerdo si se pasan varios parámetros, pero se ve feo cuando 0 o 1 parámetros son comunes. Por ejemplo: gist.github.com/andybak/b23b6ad9a68c7e1b794d
Andy Baker el
1
Sí, ese estilo tiene casos degenerados (como cualquier estilo). No rompería con todos los padres abiertos. Nada de esto me deja feliz, pero aquí hay algunos casos: gist.github.com/pkoch/8098c76614765750f769
pkoch
12

Simplemente almacene el resultado / objeto intermedio e invoque el siguiente método, p. Ej.

q = Session.query(Subkeyword.subkeyword_id, Subkeyword.subkeyword_word)
q = q.filter_by(subkeyword_company_id=self.e_company_id)
q = q.filter_by(subkeyword_word=subkeyword_word)
q = q.filter_by(subkeyword_active=True)
subkeyword = q.one()
Ivo van der Wijk
fuente
10
Esto funciona bien para algo así como una consulta, pero como patrón general, no estoy tan seguro. Por ejemplo, cuando se encadena en Beautiful Soup team_members = soup.find(class_='section team').find_all('ul').find_all('li'), el valor de retorno de cada .find(...)llamada aún no se ajusta al significado de team_members.
Taylor Edmiston
1
@TaylorEdmiston Puede tener diferentes nombres para los resultados parciales, por supuesto. Algo así como section = soup.find(class_='section team')y team_members = section.find_all('ul').find_all('li').
Jeyekomon
4

De acuerdo con la Referencia del lenguaje Python
Puede usar una barra invertida.
O simplemente romperlo. Si un paréntesis no está emparejado, python no lo tratará como una línea. Y bajo tal circunstancia, la sangría de las siguientes líneas no importa.

Haozhun
fuente
4

Es una solución un poco diferente a la proporcionada por otros, pero una de mis favoritas, ya que a veces conduce a una ingeniosa metaprogramación.

base = [Subkeyword.subkeyword_id, Subkeyword_word]
search = {
    'subkeyword_company_id':self.e_company_id,
    'subkeyword_word':subkeyword_word,
    'subkeyword_active':True,
    }
subkeyword = Session.query(*base).filter_by(**search).one()

Esta es una buena técnica para crear búsquedas. Revise una lista de condicionales para extraer de su formulario de consulta complejo (o deducciones basadas en cadenas sobre lo que está buscando el usuario), luego simplemente explote el diccionario en el filtro.

Árni St. Sigurðsson
fuente
1

Parece que usa SQLAlchemy, si es cierto, el sqlalchemy.orm.query.Query.filter_by()método toma múltiples argumentos de palabras clave, por lo que podría escribir como:

subkeyword = Session.query(Subkeyword.subkeyword_id,
                           Subkeyword.subkeyword_word) \
                    .filter_by(subkeyword_company_id=self.e_company_id,
                               subkeyword_word=subkeyword_word,
                               subkeyword_active=True) \
                    .one()

Pero sería mejor:

subkeyword = Session.query(Subkeyword.subkeyword_id,
                           Subkeyword.subkeyword_word)
subkeyword = subkeyword.filter_by(subkeyword_company_id=self.e_company_id,
                                  subkeyword_word=subkeyword_word,
                                  subkeyword_active=True)
subkeuword = subkeyword.one()
minhee
fuente
+1 para la sugerencia SQLAlchemy filter_by (). Es bueno para este ejemplo, pero a menudo uso filter () en su lugar, que acepta solo 1 condición.
Juliusz Gonera
1

Me gusta sangrar los argumentos por dos bloques, y la declaración por un bloque, como estos:

for image_pathname in image_directory.iterdir():
    image = cv2.imread(str(image_pathname))
    input_image = np.resize(
            image, (height, width, 3)
        ).transpose((2,0,1)).reshape(1, 3, height, width)
    net.forward_all(data=input_image)
    segmentation_index = net.blobs[
            'argmax'
        ].data.squeeze().transpose(1,2,0).astype(np.uint8)
    segmentation = np.empty(segmentation_index.shape, dtype=np.uint8)
    cv2.LUT(segmentation_index, label_colours, segmentation)
    prediction_pathname = prediction_directory / image_pathname.name
    cv2.imwrite(str(prediction_pathname), segmentation)
acgtyrant
fuente