Executando verificação de segurança...
3

[Python] Função anônima em dicionário

Essa forma de estruturar o código é mais usada no Javascript/NodeJS pelo fato da popularização das arrow functions, que é um das formas de se criar uma função anônima.

Usar funções anônimas em um dicionário é muito útil, mas antes de chegarmos aos exemplos, é de bom-tom explicar rasamente os "componentes" necessários para utilizar desta estrutura.

O que é um dicionário?

Um dicionário ( dict ) é uma coleção de itens regido por chaves e valores.

data = { "key": "value" }

print(data.key)

#output: "value"

O que é uma função anônima?

Uma função anônima ( lambda ) é aquela criada sem necessidade de um nome.

#função lambda
lambda x, y: x + y 

#função normal
def soma(x, y):
    return x + y

Por que usar funções anônimas em dicionários?

Essa estrutura visa reduzir o número de condições no código, e também se revela bem eficiente em gerenciar sockets, inclusive, o exemplo que será mostrado agora usa um servidor e um cliente socket.

import os
import socket
import threading

def ConnectionManager(connection, address):
    print(f"New connection from: {address}\n")

    while True:
        data = bytes.decode(connection.recv(1024))
        if data == "username":
            connection.send(bytes(os.getenv("USERNAME"), "utf-8"))
                
        elif data == "machine":
            connection.send(bytes(os.getenv("COMPUTERNAME"), "utf-8"))
            
        else:
            connection.send(bytes("Command not recognized", "utf-8"))
        

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.bind(("127.0.0.1", 8080))
    server.listen()

    while True:
        connection, address = server.accept()
        threading.Thread(target=ConnectionManager, args=(connection, address)).start()

O código acima cria um servidor socket na porta 8080 com threads, dando direito a conexões simultâneas.
Podemos ver que dentro do loop da função ConnectionManager existem três condições:

  1. Primeira condição ( if ) encaminha o fluxo do código para enviar ao cliente o nome de usuário do computador.
  2. Segunda condição ( elif ) encaminha o fluxo do código para enviar ao cliente o nome do computador.
  3. Terceira condição ( else ) encaminha o fluxo do código para enviar ao cliente que não foi possível reconhecer o comando envido.
if data == "username":
    connection.send(bytes(os.getenv("USERNAME"), "utf-8"))
                
elif data == "machine":
    connection.send(bytes(os.getenv("COMPUTERNAME"), "utf-8"))

Agora vamos reescrever esse mesmo código, porém, introduzindo a respectiva estrutura:

import os
import socket
import threading

commands = {
    "username": lambda connection: ( connection.send(bytes(os.getenv("USERNAME"), "utf-8")) ),
    "machine": lambda connection: ( connection.send(bytes(os.getenv("COMPUTERNAME"), "utf-8")) ),
    "exception": lambda connection: ( connection.send(bytes("Command not recognized", "utf-8")) )
}

def ConnectionManager(connection, address):
    print(f"New connection from: {address}\n")

    while True:
        data = bytes.decode(connection.recv(1024))
        command = commands[data]
        if command: command(connection)
        else: commands["exception"]()
        

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.bind(("127.0.0.1", 8080))
    server.listen()

    while True:
        connection, address = server.accept()
        threading.Thread(target=ConnectionManager, args=(connection, address)).start()

Você pode observar duas alterações no script:

  1. Existência de uma variável contendo um dicionário logo após os ( imports ).
  2. A remoção das condições essenciais para encaminhar a resposta ao cliente.
commands = {
    "username": lambda connection: (
        connection.send(bytes(os.getenv("USERNAME"), "utf-8"))
    ),

    "machine": lambda connection: (
        connection.send(bytes(os.getenv("COMPUTERNAME"), "utf-8"))
    ),
    
    "exeption": lambda connection: (
        connection.send(bytes("Command not recognized", "utf-8"))
    )
}

Dentro da chave "username" existe uma função com um argumento connection. Esse argumento é responsável por nos enviar a variável, na qual está armazenada a conexão com o cliente.
Assim que o comando "username" é executado, ele converte os dados de string para bytes e envia a resposta ao cliente.

O mesmo processo ocorre com o comando "machine" e "exeption", somente os dados que são enviados ao cliente são alterados.

data = bytes.decode(connection.recv(1024))
command = commands[data]
if command: command(connection)
else: commands["exeption"]()

As condições dentro do loop na função ConnectionManager são substituídos por uma variável e uma condição ( if ) mostrados acima. A variável contém o comando a ser executado, e a condição verifica se o comando existe dentro do dicionário.

A baixo pode-se ver o cliente que irá enviar os comandos e receber os dados.

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect(("127.0.0.1", 8080))
    while True:
        data = input("Send: ")
        client.send(bytes(data, "utf-8"))
        print(client.recv(1024))

Mas esses códigos, apesar de serem diferentes uns dos outros, desempenham a mesma função, porém com menos condições e mais organização.

Vale destacar que quanto maior a complexidade das funções, mais eficiente será aplicar essa estrutura no seu código.

Obs: Ao final da função anônima com mais de uma linha de código, é obrigatorio colocar uma virgula para finalizar a linha, por se tratar de uma tupla.

Bonus 🥳

O script a seguir é o que gerencia arquivos de áudio. Por meio de reconhecimento de voz ele executa certas funções armazenadas em um dicionário, podendo reproduzir os arquivos de áudio e interromper a reprodução dos mesmos.

import pygame
import threading
import speech_recognition as sr

speaker = pyttsx3.init()

current_play = ""

commands = {
    "tocar": lambda: (
        pygame.mixer.init(),
        pygame.mixer.music.load("./resources/music.mp3"),
        pygame.mixer.music.play()
    ),

    "parar": lambda: (
        pygame.mixer.music.pause()
    ),
}

while True:
    microphone = sr.Recognizer()
    with sr.Microphone() as source:
        microphone.adjust_for_ambient_noise(source)
    
        print("Listen...")
    
        audio = microphone.listen(source)
        
        try:
            data = str(microphone.recognize_google(audio, language='pt-BR')).split(" ")
            print(data)
            
            if data[0] == "Jeffrey":
                command = commands[data[1].lower()]
                if command:
                    threading.Thread(target=command).start()
        
        except sr.UnknownValueError:
            pass
Carregando publicação patrocinada...
2

Achei o post muito interessante, parabéns pro autor!
Queria acrescentar algumas coisas:

Trabalho com ciência de dados e funções lambda fazem parte do meu dia a dia, pois são uma excelente forma de fazer alterações em grandes conjuntos de dados (normalmente armazenados em DataFrames Pandas).

  • Exemplo: Digamos que você tenha um DataFrame composto pelas colunas "col1" e "col2" e você deseja aplicar uma lógica complexa utilizando ambas. Com funções lambda isso fica muito simples:
df.apply(lambda x: func_complex(x['col1'], x['col2']), axis=1)

O uso do axis=1 é para aplicar a função linha a linha da tabela. Se utilizassemos axis=0 seria aplicado nas colunas.

1
1

Bom dia salles,

Legal falar sobre funções anônimas, somente uma palavra de cautela é que as funções anônimas reduzem a legibilidade do código.

1

O mais legal de um código bem escrito, organizado e com namespaces adequados, é que ele é auto documentado, dispensando qualquer comentário.
Basta lermos o código/função e já sabemos o que faz e qual a sua finalidade.
Parabéns.