Foto: Chris RiedEn este artículo, comprenderá qué es un paradigma funcional y cómo usar la programación funcional en Python. También aprenderá sobre la
abstracción de listas y otras comprensiones de listas.
Paradigma funcional
En un paradigma imperativo, usted escribe un programa especificando una secuencia de acciones que luego se realizan. En este momento, los estados (
traductor aprox .: variables, matrices, etc. ) cambian. Por ejemplo, deje que la variable A almacene el valor 5, luego cambie el valor de esta variable. Utiliza variables para que sus valores cambien.
En un paradigma funcional, no le dice a la computadora qué hacer, sino que especifica la naturaleza de las acciones mismas. ¿Cuál es el máximo divisor común de un número, el resultado de cálculos de 1 a n, etc.
Por lo tanto, las variables no cambian. Una vez que se inicializa una variable, su valor se guarda para siempre (tenga en cuenta que en lenguajes funcionales puros ni siquiera se llaman variables). Por lo tanto, en el paradigma funcional, las funciones no tienen
efectos secundarios . Un efecto secundario se puede definir como el momento durante el cual una función cambia algo más allá de sus límites. Echa un vistazo a un ejemplo:
a = 3 def some_func(): global a a = 5 some_func() print(a)
El resultado de la ejecución de este código es 5. En la programación funcional, el cambio de variables está prohibido y las funciones de algo más allá de sus fronteras también. Todo lo que la función puede hacer es calcular / procesar algo y devolver el resultado.
Ahora, podría estar pensando: “¿Sin variables, sin efectos secundarios? ¿Por qué es eso bueno? Muy buena pregunta.
Si una función ha sido llamada dos veces con los mismos parámetros, obviamente devolverá el mismo resultado. Si ha estudiado algo sobre
las funciones matemáticas , apreciará esta oportunidad. Esto se llama transparencia de enlace o transparencia referencial. Como las funciones no tienen efectos secundarios, si está desarrollando un programa de cálculo, puede acelerar el proceso de ejecución. Si el programa sabe que func (2) es 3, podemos recordar esto. Esto evita que se vuelva a llamar a la función cuando ya conocemos el resultado.
Por lo general, en la programación funcional, no se utilizan bucles. Se usa la recursión. La recursión es un concepto matemático, de hecho, significa "alimentarse algo a uno mismo". En una función recursiva, la función misma se llama a sí misma el papel de una subfunción. Aquí hay un ejemplo de una función recursiva en Python:
def factorial_recursive(n):
Algunos lenguajes de programación son
flojos . Esto significa que están calculando todo en el último momento. Suponga que si el código ejecuta 2 + 2, el programa funcional calculará el resultado solo cuando sea necesario. Aprenderemos sobre la pereza de Python un poco más tarde.
Mapa
Para comprender el mapa, primero debe tratar con contenedores iterables. Este es un contenedor en el que puedes "ir". A menudo son listas o matrices, pero hay muchos contenedores de este tipo en Python. Incluso puedes crear tu propio contenedor introduciendo
métodos mágicos . Estos métodos, como las API, ayudan a que los objetos se vuelvan más pitónicos. Existen 2 métodos para hacer que un objeto sea iterable:
class Counter: def __init__(self, low, high):
El primer método mágico es "___iter__" o dunder (doble subrayado por guiones bajos) iter devuelve un objeto iterable, esto a menudo se usa al comienzo de un ciclo. Dunder next (__next__) devuelve el siguiente objeto.
Mira esto:
for c in Counter(3, 8): print(c)
Resultado de ejecución:
3
4
5
6
7
8
En Python, un iterador es un objeto que solo tiene el método __iter__. Esto significa que puede acceder al lugar de las celdas del objeto (contenedor), pero no puede "caminar" a través de ellas. Algunos objetos solo tienen el maravilloso método __next__, sin el método mágico __iter__, por ejemplo, set (más sobre eso más adelante). En este artículo, cubriremos todo lo relacionado con objetos iterables.
Ahora que sabemos qué es un objeto iterable, volvamos a la función de mapa. Esta función nos permite aplicar la acción de alguna otra función a cada elemento en un contenedor iterado. Queremos aplicar una función a cada elemento de la lista, esto es posible para casi todos los contenedores iterables. Map, toma dos argumentos: la función que se aplicará y el contenedor (lista, etc.).
map(function, iterable)
Supongamos que tenemos una lista con los siguientes elementos:
[1, 2, 3, 4, 5]
Y queremos cuadrar cada elemento, esto se puede hacer así:
x = [1, 2, 3, 4, 5] def square(num): return num*num print(list(map(square, x)))
Las funciones funcionales en Python son perezosas. Si no agregamos "list ()", la función almacenará la descripción del contenedor (list), y no la lista misma. Tenemos que decirle directamente a Python que convierta esto en una lista.
Es un poco extraño pasar de una definición no perezosa a una perezosa tan repentinamente. Te acostumbrarás si piensas más de una manera funcional que imperativa.
Escribir funciones, por ejemplo, "cuadrado (num)" es normal, pero no del todo correcto. ¿Necesitamos declarar una función completa solo para usarla en el mapa? Esto se puede simplificar introduciendo funciones lambda (anónimas).
Expresiones lambda
Las expresiones lambda son funciones en una línea, por ejemplo, aquí hay una expresión lambda que cuadra el número resultante:
square = lambda x: x * x
Y ejecuta esto:
>>> square(3)
9
Te puedo escuchar "Brandon, ¿dónde están los argumentos?" ¿De qué se trata todo esto? Esto no es como una función ".
Sí, puede ser confuso, pero puede explicarse. En esta línea, asignamos algo a la variable "cuadrado". Esta parte:
lambda x: x * x
Le dice a Python que estamos usando la función lambda, y la entrada se llama x. Todo lo que está después de los dos puntos es lo que sucederá con la entrada, y automáticamente obtendremos el resultado más adelante.
Para nuestro programa tomó la forma de una línea, debe hacer esto:
x = [1, 2, 3, 4, 5] print(list(map(lambda num: num * num, x)))
Entonces, en las expresiones lambda, los argumentos están a la izquierda y las acciones en ellos están a la derecha. Esto es un poco desordenado, nadie lo niega. La verdad es que hay algo en eso, para escribir ese código funcional. Además, es muy bueno convertir funciones a una sola línea.
Reducir
Reducir es una función que convierte un contenedor iterable en una cosa. Es decir, se realizan cálculos que convierten la lista en un solo número. Se ve así:
reduce(function, list)
Podemos (y con frecuencia lo haremos) usar funciones lambda como argumento de la función.
Si queremos multiplicar todos los números en la lista, esto se puede hacer así:
product = 1 x = [1, 2, 3, 4] for num in x: product = product * num
Y con reduce se verá así:
from functools import reduce product = reduce((lambda x, y: x * y),[1, 2, 3, 4])
El resultado será el mismo, pero el código es más corto y con el conocimiento de programación funcional para usarlo con mayor precisión.
Filtro
La función de filtro toma un contenedor iterable y lo filtra de acuerdo con una regla dada (también una función).
Por lo general, toma una función y una lista como entrada. Más tarde, aplica una función a cada elemento de la lista, si la función devuelve Verdadero, no sucede nada y, si es Falso, el elemento se elimina de la lista.
Sintaxis:
filter(function, list)
Veamos un ejemplo sin usar filtro:
x = range(-5, 5) new_list = [] for num in x: if num < 0: new_list.append(num)
Junto con filtro:
x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x))
Funciones de orden superior
Las funciones de orden superior pueden tomar funciones como argumentos y devolverlos. Un ejemplo simple se vería así:
def summation(nums): return sum(nums) def action(func, numbers): return func(numbers) print(action(summation, [1, 2, 3]))
O un ejemplo es aún más simple:
def rtnBrandon(): return "brandon" def rtnJohn(): return "john" def rtnPerson(): age = int(input("What's your age?")) if age == 21: return rtnBrandon() else: return rtnJohn()
Recuerde que antes dije que la programación funcional real no usa variables. Las funciones de orden superior lo hacen posible. No necesita guardar la variable en algún lugar si está pasando información a través de un largo "túnel" de funciones.
Todas las funciones en Python son objetos de primera clase. Un objeto de la primera clase se define como tal, que corresponde a uno o más de los siguientes parámetros:
- Crea un ciclo de trabajo.
- Asignado a una variable o elemento en una estructura de datos
- Pasado como argumento de función
- Devuelto como resultado de la ejecución de la función
Por lo tanto, todas las funciones en Python son objetos de primera clase y pueden usarse como funciones de orden superior.
Aplicación parcial
El uso parcial (también la interrupción) es un poco extraño, pero muy bueno. Puede llamar a la función sin utilizar todos los argumentos dados. Veamos un ejemplo. Queremos crear una función que tome 2 argumentos, la base y el grado, y devuelva la base elevada a la potencia, se ve así:
def power(base, exponent): return base ** exponent
Ahora necesitamos crear una función separada para cuadrar y calcularla usando la función de potencia:
def square(base): return power(base, 2)
Funciona, pero ¿y si queremos poner un número al cubo? O en el 4to grado? ¿Deberías escribir tales funciones para siempre? Por supuesto que puedes. Pero los programadores son flojos. Si repite lo mismo varias veces, probablemente haya una manera de hacerlo más rápido y dejar de hacer repeticiones. La aplicación parcial se puede utilizar aquí. Veamos un ejemplo de la función de potencia usando una aplicación parcial:
from functools import partial square = partial(power, exponent=2) print(square(2))
¿No es genial? Podemos llamar a una función que necesita 2 argumentos, usando solo 1 y especificando cuál será el segundo por sí solo.
También puede usar un bucle para simular una función de potencia que funcionará con cubos hasta la potencia número 1000.
from functools import partial powers = [] for x in range(2, 1001): powers.append(partial(power, exponent = x)) print(powers[0](3))
La programación funcional no coincide con los cánones pitónicos.
Es posible que haya notado que muchas de las cosas que queremos hacer en la programación funcional giran en torno a las listas. Además de la función de reducción y la aplicación parcial, todas las funciones que vio generan listas. A Guido (el creador de Python`a) no le gustan las cosas funcionales en Python`e, ya que Python tiene su propio método para crear listas.
Si escribe "importar esto" en la consola, obtendrá:
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one — and preferably only one — obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let's do more of those!
Este es Python Zen. Este es un verso sobre lo que significa ser un pitonista. La parte que nos interesa es:
Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.
Debería haber solo una, y preferiblemente solo una, forma obvia de hacer algo.
En Python, el mapa y el filtro pueden hacer lo mismo que la abstracción de lista (
enlace ). Esto viola una de las reglas de Python-Zen, por lo que esta parte de la programación funcional no es "pitónica".
Las siguientes cosas para hablar son la función lambda. En Python, una función lambda es una función normal. Y de hecho es azúcar sintáctico. Ambas partes hacen lo mismo:
foo = lambda a: 2 def foo(a): return 2
Una función estándar puede seguir siendo la misma que una función lambda, pero no al revés. Una función Lambda no puede ser la misma que una normal.
Este fue un pequeño comentario sobre por qué la programación funcional no se ajusta del todo a la ideología pitónica. Anteriormente, mencioné la abstracción de listas (
también inclusión de listas ), ahora hablemos de ello.
Lista de abstracción
Ya he dicho que todo lo que se puede hacer usando el mapa y el filtro se puede hacer usando la abstracción de la lista. En esta parte lo discutiremos.
La abstracción de listas es una forma de crear listas en Python. Sintaxis:
[function for item in iterable]
Vamos a cuadrar cada elemento de la lista, por ejemplo:
print([x * x for x in [1, 2, 3, 4]])
De acuerdo, podemos ver cómo aplicar la función a cada elemento de la lista. ¿Cómo nos movemos por el filtro? Echa un vistazo a este código:
x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x)) print(all_less_than_zero)
Ahora usa la abstracción de la lista:
x = range(-5, 5) all_less_than_zero = [num for num in x if num < 0]
La abstracción de lista admite expresiones condicionales de este tipo. Ya no necesita usar un millón de funciones para obtener algo. De hecho, si está intentando hacer algo con una lista, existe la posibilidad de que sea más limpio y fácil de lograr con la abstracción de las listas.
¿Qué pasa si queremos cuadrar cada elemento de la lista que está debajo de cero? Con la función lambda, mapa y filtro, se verá así:
x = range(-5, 5) all_less_than_zero = list(map(lambda num: num * num, list(filter(lambda num: num < 0, x))))
Esta entrada no es racional ni muy simple. Usando la abstracción de la lista, se verá así:
x = range(-5, 5) all_less_than_zero = [num * num for num in x if num < 0]
La abstracción de la lista solo es buena, por extraño que parezca, para las listas. El mapa y el filtro funcionan para cada contenedor iterable, ¿qué pasa? Sí, puede usar la abstracción para cada contenedor iterable que encuentre.
Otras abstracciones
Puede aplicar la abstracción para cada contenedor iterable.
Cada contenedor iterable se puede crear mediante abstracción. A partir de la versión 2.7, incluso puede crear un diccionario (tabla hash).
Si algo es un contenedor iterable, entonces se puede generar algo. Veamos el último ejemplo usando set. Si no sabe qué conjunto es, entonces vea
este artículo escrito por mí también. En resumen:
- Set es un contenedor de elementos; los elementos en él no se repiten
- El orden no es importante
Como habrás notado, set, como un diccionario, usa llaves. Python es realmente inteligente. Adivinará si está utilizando una abstracción de diccionario o un conjunto de abstracción, en función de si especifica parámetros adicionales para el diccionario o no. Si quieres saber más sobre abstracciones, lee
esto . Si se trata de abstracciones y generación, entonces
esta .
Resumen
La programación funcional es genial. El código funcional puede ser limpio o no muy. Algunos pitonistas incondicionales no aceptan el paradigma funcional en Python. Debes usar lo que quieras y lo que más te convenga.
Página del autor