Sintaxe por trás da classificação (chave = lambda:…)

152

Não entendo bem a sintaxe por trás do sorted()argumento:

key=lambda variable: variable[0]

Não é lambdaarbitrário? Por que é variableindicado duas vezes no que parece ser um dict?

Christopher Markieta
fonte

Respostas:

162

keyé uma função que será chamada para transformar os itens da coleção antes de serem comparados. O parâmetro passado para keydeve ser algo que pode ser chamado.

O uso de lambdacria uma função anônima (que pode ser chamada). No caso do sortedcallable leva apenas um parâmetro. O Python lambdaé bem simples. Só pode fazer e retornar uma coisa realmente.

A sintaxe de lambdaé a palavra lambdaseguida pela lista de nomes de parâmetros e, em seguida, um único bloco de código. A lista de parâmetros e o bloco de código são delineados por dois pontos. Isso é semelhante a outras construções no pitão, bem como while, for, ife assim por diante. São todas as instruções que normalmente têm um bloco de código. O Lambda é apenas mais uma instância de uma instrução com um bloco de código.

Podemos comparar o uso de lambda com o de def para criar uma função.

adder_lambda = lambda parameter1,parameter2: parameter1+parameter2
def adder_regular(parameter1, parameter2): return parameter1+parameter2

O lambda apenas nos fornece uma maneira de fazer isso sem atribuir um nome. O que o torna ótimo para usar como parâmetro em uma função.

variable é usado duas vezes aqui, porque no lado esquerdo dos dois pontos é o nome de um parâmetro e, no lado direito, está sendo usado no bloco de código para calcular algo.

Evan
fonte
10
note note (para OP): deve-se evitar atribuir um lambda a um nome (ou seja, usá-lo de maneira diferente da função anônima). se você estiver fazendo isso, provavelmente deve usar a def.
Wim
151

Eu acho que todas as respostas aqui cobrem o núcleo do que a função lambda faz no contexto de classificado () bastante bem, no entanto, ainda me sinto uma descrição que leva a um entendimento intuitivo, então aqui estão meus dois centavos.

Por uma questão de completude, declararei o óbvio de antemão: sorted () retorna uma lista de elementos classificados e se queremos classificar de uma maneira específica ou se queremos classificar uma lista complexa de elementos (por exemplo, listas aninhadas ou uma lista de tuplas), podemos invocar o argumento principal.

Para mim, o entendimento intuitivo do argumento principal, por que ele deve ser exigível e o uso do lambda como a função solicitável (anônima) para realizar isso ocorre em duas partes.

  1. O uso do lamba significa que você não precisa escrever (definir) uma função inteira, como a que o sblom forneceu como exemplo. As funções do Lambda são criadas, usadas e imediatamente destruídas - para que elas não alterem seu código com mais códigos que serão usados ​​apenas uma vez. Esse, pelo que entendi, é o principal utilitário da função lambda e seus aplicativos para tais funções são amplos. Sua sintaxe é puramente por convenção, que é essencialmente a natureza da sintaxe programática em geral. Aprenda a sintaxe e termine com ela.

A sintaxe do Lambda é a seguinte:

lambda input_variable (s) : saboroso forro

por exemplo

In [1]: f00 = lambda x: x/2

In [2]: f00(10)
Out[2]: 5.0

In [3]: (lambda x: x/2)(10)
Out[3]: 5.0

In [4]: (lambda x, y: x / y)(10, 2)
Out[4]: 5.0

In [5]: (lambda: 'amazing lambda')() # func with no args!
Out[5]: 'amazing lambda'
  1. A idéia por trás do keyargumento é que ele deve receber um conjunto de instruções que apontarão essencialmente a função 'sorted ()' para os elementos da lista que devem ser usados ​​para ordenar por. Quando diz key=, o que realmente significa é: À medida que eu percorre a lista um elemento de cada vez (por exemplo, para e na lista), vou passar o elemento atual para a função que forneço no argumento-chave e usar esse para criar uma lista transformada que me informará na ordem da lista final classificada.

Confira:

mylist = [3,6,3,2,4,8,23]
sorted(mylist, key=WhatToSortBy)

Exemplo básico:

sorted(mylist)

[2, 3, 3, 4, 6, 8, 23] # todos os números estão em ordem, de pequeno a grande.

Exemplo 1:

mylist = [3,6,3,2,4,8,23]
sorted(mylist, key=lambda x: x%2==0)

[3, 3, 23, 6, 2, 4, 8] # Este resultado classificado faz sentido para você?

Observe que minha função lambda disse ordenada para verificar se (e) era par ou ímpar antes da classificação.

MAS ESPERE! Você pode (ou talvez deva) estar se perguntando duas coisas - primeiro, por que minhas chances estão diante dos meus pares (já que meu valor-chave parece estar dizendo à minha função ordenada que priorize os pares usando o operador mod emx%2==0 ). Segundo, por que meus pares estão fora de ordem? 2 vem antes das 6, certo? Analisando esse resultado, aprenderemos algo mais profundo sobre como o argumento 'key' classificado () funciona, especialmente em conjunto com a função lambda anônima.

Primeiro, você notará que, embora as probabilidades cheguem antes dos pares, os próprios pares não são classificados. Por que é isso?? Vamos ler os documentos :

Funções-chave A partir do Python 2.4, list.sort () e sorted () adicionaram um parâmetro-chave para especificar uma função a ser chamada em cada elemento da lista antes de fazer comparações.

Temos que ler um pouco as entrelinhas aqui, mas o que isso nos diz é que a função de classificação é chamada apenas uma vez e, se especificarmos o argumento da chave, classificaremos pelo valor que a função da chave nos aponta.

Então, o que o exemplo usando um módulo retorna? Um valor booleano: True == 1, False == 0. Então, como os classificados lidam com essa chave? Basicamente, transforma a lista original em uma sequência de 1s e 0s.

[3,6,3,2,4,8,23] torna-se [0,1,0,1,1,1,0]

Agora estamos chegando a algum lugar. O que você ganha quando classifica a lista transformada?

[0,0,0,1,1,1,1]

Certo, agora sabemos por que as probabilidades vêm antes dos céus. Mas a próxima pergunta é: Por que os 6 ainda vêm antes dos 2 na minha lista final? Bem, isso é fácil - é porque a classificação acontece apenas uma vez! ou seja, esses 1s ainda representam os valores da lista original, que estão em suas posições originais uma em relação à outra. Como a classificação ocorre apenas uma vez e não chamamos nenhum tipo de função de classificação para ordenar os valores pares originais de baixo para alto, esses valores permanecem em sua ordem original em relação um ao outro.

A questão final é a seguinte: como conceitualmente como a ordem dos meus valores booleanos é transformada novamente nos valores originais quando imprimo a lista final classificada?

Sorted () é um método interno que (engraçado) usa um algoritmo de classificação híbrido chamado Timsortque combina aspectos de classificação de mesclagem e inserção. Parece-me claro que quando você o chama, existe um mecânico que mantém esses valores na memória e os agrupa com sua identidade booleana (máscara) determinada pela (...!) Função lambda. A ordem é determinada por sua identidade booleana calculada a partir da função lambda, mas lembre-se de que essas sublistas (de um e zeros) não são elas próprias classificadas por seus valores originais. Portanto, a lista final, embora organizada por Odds e Evens, não é classificada por sub-lista (os pares neste caso estão fora de ordem). O fato de que as probabilidades são ordenadas é porque elas já estavam em ordem por coincidência na lista original. O argumento de tudo isso é que, quando o lambda faz essa transformação, a ordem original dos sublistas é mantida.

Então, como isso tudo se relaciona com a pergunta original e, mais importante, com a nossa intuição de como devemos implementar a ordenação ordenada () com seu argumento principal e lambda?

Essa função lambda pode ser vista como um ponteiro que aponta para os valores que precisamos classificar, seja um ponteiro que mapeie um valor para seu booleano transformado pela função lambda ou se é um elemento específico em uma lista aninhada, tupla, dict, etc., novamente determinado pela função lambda.

Vamos tentar prever o que acontece quando executo o código a seguir.

mylist = [(3, 5, 8), (6, 2, 8), ( 2, 9, 4), (6, 8, 5)]
sorted(mylist, key=lambda x: x[1])

Minha sortedligação obviamente diz: "Classifique esta lista". O argumento-chave torna isso um pouco mais específico dizendo, para cada elemento (x) na minha lista, retorne o índice 1 desse elemento e, em seguida, classifique todos os elementos da lista original 'minha lista' pela ordem classificada da lista calculada por a função lambda. Como temos uma lista de tuplas, podemos retornar um elemento indexado dessa tupla. Então temos:

[(6, 2, 8), (3, 5, 8), (6, 8, 5), (2, 9, 4)]

Execute esse código e você descobrirá que esse é o pedido. Tente indexar uma lista de números inteiros e você verá que o código quebra.

Essa foi uma explicação longa, mas espero que isso ajude a 'classificar' sua intuição sobre o uso de funções lambda como o argumento principal em classificado () e além.

PaulG
fonte
8
explicação excelente e abrangente. Esta resposta merece 100 pontos. Mas eu me pergunto por que gostos são menos nessa resposta.
javed
3
Obrigado pela explicação profunda. Eu li os documentos e classifiquei como fazer e não estava clara para mim a ideia por trás da keyfunção. Se você está tentando entender a sortedfunção, a lambdasintaxe entra no caminho da compreensão.
sanbor
3
Esta é a melhor explicação aqui. Isso foi realmente útil para me ajudar a entender como isso realmente funcionava - eu meio que compreendi a função lambda, mas usá-la no contexto de classificado () não fazia sentido. Isso realmente ajudou, obrigado!
TGWaffles
2
Esta é uma resposta brilhante. Saudá-lo, senhor.
Clive Mappu
2
Esta é uma das minhas respostas favoritas no estouro de pilha. Obrigado!
AdR
26

lambdaé uma palavra-chave Python usada para gerar funções anônimas .

>>> (lambda x: x+2)(3)
5
Ignacio Vazquez-Abrams
fonte
2
Por que existem parênteses em torno de cada um?
Christopher Markieta
19
As parênteses existem 3porque estão sendo passadas para uma função. As parênteses estão ao redor do lambda para que a expressão não seja analisada como lambda x: x+2(3), o que é inválido, pois 2não é uma função.
Ignacio Vazquez-Abrams
Não sei se gosto do termo funções "anônimas". Quero dizer, é verdade que eles não são nomeados, então o anônimo é "tecnicamente" preciso. Prefiro me referir a eles como "funções temporárias". Mas então, eu sou um pedante.
user5179531
12

O variablelado esquerdo do :é um nome de parâmetro. O uso devariable à direita está usando o parâmetro

Significa quase exatamente o mesmo que:

def some_method(variable):
  return variable[0]
sblom
fonte
5

Mais um exemplo de uso da função classificada () com chave = lambda. Vamos considerar que você tem uma lista de tuplas. Em cada tupla, você tem uma marca, modelo e peso do carro e deseja classificar essa lista de tuplas por marca, modelo ou peso. Você pode fazer isso com lambda.

cars = [('citroen', 'xsara', 1100), ('lincoln', 'navigator', 2000), ('bmw', 'x5', 1700)]

print(sorted(cars, key=lambda car: car[0]))
print(sorted(cars, key=lambda car: car[1]))
print(sorted(cars, key=lambda car: car[2]))

Resultados:

[('bmw', 'x5', '1700'), ('citroen', 'xsara', 1100), ('lincoln', 'navigator', 2000)]
[('lincoln', 'navigator', 2000), ('bmw', 'x5', '1700'), ('citroen', 'xsara', 1100)]
[('citroen', 'xsara', 1100), ('bmw', 'x5', 1700), ('lincoln', 'navigator', 2000)]
filler36
fonte
3

lambdaé uma função anônima, não uma função arbitrária. O parâmetro aceito seria a variável com a qual você está trabalhando e a coluna na qual você está classificando.

Makoto
fonte
1

Apenas para reformular, a tecla (Opcional. Uma função a ser executada para decidir a ordem. O padrão é Nenhum) nas funções classificadas espera uma função e você usa lambda.

Para definir lambda, você especifica a propriedade do objeto que deseja classificar e a função classificada interna do python cuidará dela automaticamente.

Se você deseja classificar por várias propriedades, atribua key = lambda x: (propriedade1, propriedade2).

Para especificar a ordem, passe reverse = true como o terceiro argumento (Opcional. Um booleano. Falso classificará em ascensão, True classificará em descendente. O padrão é False) da função classificada.

kta
fonte
1

Resposta simples e não demorada, com um exemplo relevante para a pergunta feita Siga este exemplo:

 user = [{"name": "Dough", "age": 55}, 
            {"name": "Ben", "age": 44}, 
            {"name": "Citrus", "age": 33},
            {"name": "Abdullah", "age":22},
            ]
    print(sorted(user, key=lambda el: el["name"]))
    print(sorted(user, key= lambda y: y["age"]))

Veja os nomes na lista, eles começam com D, B, C e A. E se você notar as idades, eles são 55, 44, 33 e 22. O primeiro código de impressão

print(sorted(user, key=lambda el: el["name"]))

Resultados para:

[{'name': 'Abdullah', 'age': 22}, 
{'name': 'Ben', 'age': 44}, 
{'name': 'Citrus', 'age': 33}, 
{'name': 'Dough', 'age': 55}]

classifica o nome porque, por key = lambda el: el ["name"], estamos classificando os nomes e os nomes retornam em ordem alfabética.

O segundo código de impressão

print(sorted(user, key= lambda y: y["age"]))

Resultado:

[{'name': 'Abdullah', 'age': 22},
 {'name': 'Citrus', 'age': 33},
 {'name': 'Ben', 'age': 44}, 
 {'name': 'Dough', 'age': 55}]

classifica por idade e, portanto, a lista retorna por ordem crescente de idade.

Experimente este código para entender melhor.

AbdullahS96
fonte