Como quebrar uma linha de métodos encadeados em Python?

137

Eu tenho uma linha do seguinte código (não culpe as convenções de nomenclatura, elas não são minhas):

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()

Não gosto da aparência (não muito legível), mas não tenho nenhuma idéia melhor para limitar as linhas a 79 caracteres nessa situação. Existe uma maneira melhor de quebrá-lo (de preferência sem barras invertidas)?

Juliusz Gonera
fonte

Respostas:

255

Você pode usar parênteses adicionais:

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()
    )
sth
fonte
Eu também gosto mais. Não adiciona mais código e é sem barras invertidas.
Juliusz Gonera
22
Não tenho certeza do que justifica o recuo extra aqui; Penso que esta solução também funciona bem com as linhas penduradas recuadas apenas uma vez e com o parênteses à direita.
Carl Meyer
4
Na minha opinião, o recuo duplo é útil aqui porque é visualmente distinto de um bloco recuado normal. Quando cercado por outro código, isso torna mais óbvio que é uma única linha quebrada.
sth
1
Melhor resposta, em termos de uso de parênteses. Como mencionado em um comentário por Shanimal em outra resposta, usando continuação de linha implícita via parênteses é realmente PEP 8 preferido vs o caractere de continuação de ``
kevlarr
Eu prefiro barras invertidas. Parênteses não é uma dica para todas as situações. Como exemplo, ele não funciona com o operador de atribuição. Imagine que você deseja quebrar linhas nesta cadeia:foo.set_default('bar', {}).set_default('spam', {}).set_default('eggs', {})['lol'] = 'yeah'
loutre
56

Este é um caso em que um caractere de continuação de linha é preferido para abrir parênteses. A necessidade desse estilo se torna mais óbvia à medida que os nomes dos métodos aumentam e os métodos começam a receber 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()

O PEP 8 pretende ser interpretado com uma medida de bom senso e de olho tanto na prática quanto na beleza. Felizmente, viole qualquer diretriz do PEP 8 que resulte em código feio ou difícil de ler.

Dito isto, se você frequentemente se diverte do PEP 8, pode ser um sinal de que existem problemas de legibilidade que transcendem sua escolha de espaço em branco :-)

Raymond Hettinger
fonte
2
+1 nas barras invertidas e alinhando os filtros encadeados nesse caso específico. Essa situação também surge no Django e é mais legível dessa maneira - mas em qualquer outra situação, sinto que as frases entre parênteses são superiores (não sofra com o problema "existe espaço em branco após minha barra invertida?"). Dito isto, colocar parênteses a frase pode ser usada para obter o mesmo efeito - mas o coloca no modo de leitura Lisp no meio da leitura de Python, o que eu acho chocante.
Zxq9 31/05
11
Não vejo como essa solução é mais capaz de lidar "à medida que os nomes dos métodos ficam mais longos e os métodos começam a receber argumentos" do que o "quebra automática de parênteses" ou "quebra de linha após cada ponto aberto e antes de cada ponto" soluções. De fato, é pior lidar com isso, pois (pelo menos como mostrado aqui) requer um recuo muito mais profundo para cada linha pendurada.
Carl Meyer
1
Recuo demais para as chamadas de filtro. Uma aba ou quatro espaços seriam suficientes aqui. Também alinhamento do `` ... Quantos segundos você manteve pressionada essa tecla de espaço? Geralmente sou contra todas as formas, que exigem que você martele a chave do espaço como se não houvesse amanhã.
Zelphir Kaltstahl /
2
fwiw, PEP8 diz "A maneira preferida de quebrar linhas longas é usar a continuação implícita de linhas do Python entre parênteses, colchetes e chaves. As linhas longas podem ser quebradas em várias linhas envolvendo expressões entre parênteses. Elas devem ser usadas preferencialmente ao usar uma barra invertida para continuação de linha ". - Python.org Ele passa a discutir quando as barras invertidas pode ser apropriado
Shanimal
Ótima referência ao PEP8! Um problema irritante aqui ao alinhar todas as .filterchamadas é que, se você mudar subkeywordpara sub_keyword, agora precisará corrigir o recuo de cada linha apenas porque alterou o nome da variável. Não é bom quando o estilo realmente dificulta a manutenção ...
kevlarr
15

Minha escolha pessoal seria:

subkeyword = Session.query (
    Subkeyword.subkeyword_id,
    Subkeyword.subkeyword_word,
).filtrar por(
    subkeyword_company_id = self.e_company_id,
    subkeyword_word = subkeyword_word,
    subkeyword_active = True,
).1()
pkoch
fonte
1
Concordo se há vários parâmetros sendo passados, mas parece feio quando parâmetros 0 ou 1 são comuns. Por exemplo: gist.github.com/andybak/b23b6ad9a68c7e1b794d
Andy Baker
1
Sim, esse estilo tem casos degenerados (como qualquer estilo). Eu não iria quebrar todas as parênteses abertas. Nada disso me deixa feliz, mas aqui estão alguns casos: gist.github.com/pkoch/8098c76614765750f769
pkoch
12

Apenas armazene o resultado / objeto intermediário e invoque o próximo método nele, por exemplo

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
fonte
10
Isso funciona bem para algo como uma consulta, mas como padrão geral, não tenho tanta certeza. Por exemplo, ao encadear em Sopa Bonita team_members = soup.find(class_='section team').find_all('ul').find_all('li'), o valor de retorno de cada .find(...)chamada ainda não se encaixa no significado team_members.
Taylor Edmiston
1
@TaylorEdmiston Você pode ter nomes diferentes para os resultados parciais, é claro. Algo como section = soup.find(class_='section team')e team_members = section.find_all('ul').find_all('li').
Jeyekomon 08/07/19
4

De acordo com a referência da linguagem Python
Você pode usar uma barra invertida.
Ou simplesmente quebre. Se um colchete não estiver emparelhado, o python não tratará isso como uma linha. E nessas circunstâncias, o recuo das linhas a seguir não importa.

Haozhun
fonte
4

É uma solução um pouco diferente da fornecida por outras pessoas, mas uma das minhas favoritas, pois às vezes leva a uma metaprogramação bacana.

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()

Essa é uma boa técnica para criar pesquisas. Percorra uma lista de condicionais para extrair do seu formulário de consulta complexo (ou deduções baseadas em strings sobre o que o usuário está procurando) e depois exploda o dicionário no filtro.

Árni St. Sigurðsson
fonte
1

Você parece usar SQLAlchemy, se for verdade, o sqlalchemy.orm.query.Query.filter_by()método usa vários argumentos de palavras-chave, para que você possa escrever 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()

Mas seria melhor:

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
fonte
+1 para a dica SQLAlchemy filter_by (). É bom para este exemplo, mas geralmente uso filter () que aceita apenas 1 condição.
Juliusz Gonera
1

Eu gosto de recuar os argumentos em dois blocos e a declaração em um bloco, como estes:

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)
agressivo
fonte