Python et la notation infix
sum(select(where(take_while(fib(), lambda x: x < 1000000) lambda x: x % 2), lambda x: x * x))
Rien de compliqué pourtant... Bon, coder sur une ligne, c'est mal, alors tentons d'indenter, avec un peu d'espoir :
sum(
select(
where(
take_while(
fib(),
lambda x: x < 1000000)
lambda x: x % 2)
lambda x: x * x))
fib().take_while(lambda x: x < 1000000).where(lambda x: x % 2).select(lambda x: x * x).sum()
fib()
.take_while(lambda x: x < 1000000)
.where(lambda x: x % 2)
.select(lambda x: x * x)
.sum()
fib() | take_while(lambda x: x < 1000000)
| where(lambda x: x % 2)
| select(lambda x: x * x)
| sum()
class Stdout:
def __ror__(self, value):
print value
class Double:
def __ror__(self, value):
return value * 2
stdout = Stdout()
double = Double()
42 | stdout
# 42
"foo" | stdout
# foo
42 | double | stdout
# 84
C'est un début ...
Bon jouons avec les Generateurs, maintenant...
def count():
"Creeons un rapide generateur de test, retournant 0 1 2 et 3"
yield 0
yield 1
yield 2
yield 3
class Double:
def __ror__(self, values):
for i in values:
yield i * 2
class Stdout:
def __ror__(self, values):
for i in values:
print i
double = Double()
stdout = Stdout()
count() | double | stdout
# 0 2 4 6
Tout semble fonctionner aussi avec des générateurs, mais, ce n'est pas très souple, si on pouvait factoriser ça un peu et créer une classe qui nous génèrerai des pipes, ce serait mieux, quelque chose dans ce style
count() | select(lambda x: x * 2) | stdout
def count():
yield 0
yield 1
yield 2
yield 3
class Pipe:
def __init__(self, function):
self.function = function
def __ror__(self, other):
return self.function(other)
class FuncPipe:
def __init__(self, function):
self.function = function
def __call__(self, *value):
return Pipe(lambda x: self.function(x, *value))
select = FuncPipe(lambda left, pred: (pred(x) for x in left))
where = FuncPipe(lambda left, pred: (x for x in left if pred(x)))
for i in count() | where(lambda x: x % 2 == 0) | select(lambda x: x * 2):
print i
# 0 4
Et voilà, tout semble fonctionner, il ne nous reste plus qu'a écrire quelques Pipes et quelques FuncPipes de base, et de tester !
import itertools
from random import randint
def rand(min, max):
while True:
print "Generating a random number ..."
yield randint(min, max)
class Pipe:
def __init__(self, function):
self.function = function
def __ror__(self, other):
return self.function(other)
class FuncPipe:
def __init__(self, function):
self.function = function
def __call__(self, *value):
return Pipe(lambda x: self.function(x, *value))
def _head(left, qte):
for x in left:
if qte > 0:
yield x
else:
return
qte -= 1
def _average(left):
total = 0.0
qte = 0
for x in left:
total += x
qte += 1
return total / qte
average = Pipe(_average)
head = FuncPipe(_head)
print "Average of 1000 random integers between 0 and 100 :"
ten_random_integers = rand(0, 100) | head(10)
print "Point 1"
print ten_random_integers | average
Quizz ! Que va afficher le code précédent ?
1) Rien, il part en boucle infinie...
2) Des "Generating a random number" à l'infinie
3) 10 "Generating a random number" puis "Point 1" puis le résultat
4) "Point 1" puis 10 "Generating a random number" puis le résultat ?
... ?
... ?
C'est bien le dernier qui s'affiche, et ça confirme bien que tout reste bien lazy évalué, la ligne :
ten_random_integers = rand(0, 100) | head(10)
Ne fait que décrire un générateur associé à son comportement, rand(0, 100) est un générateur infini, mais aucune valeur n'est itérée.
euler2 = fib() | where(lambda x: x % 2 == 0) | take_while(lambda x: x < 4000000) | sum
assert euler2 == 4613732
Et voilà, comme tout histoire qui se finit bien, vous finissez avec le code source : http://github.com/downloads/JulienPalard/Pype/pype.py contenant tout ce qu'il faut pour s'amuser, une bonne batterie de pipes de base, et un main de test, enjoy :-)