[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:
- Primeira condição ( if ) encaminha o fluxo do código para enviar ao cliente o nome de usuário do computador.
- Segunda condição ( elif ) encaminha o fluxo do código para enviar ao cliente o nome do computador.
- 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:
- Existência de uma variável contendo um dicionário logo após os ( imports ).
- 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