- Pengenalan umum
- FP
- Pengantar FP
- Prinsip dasar FP
- Istilah dasar
- Perilaku FP bawaan dengan Python
- Pustaka Xoltar Toolkit
- Mengembalikan perpustakaan
- literatur
- Generator
- Pengantar iterator
- Pengantar generator
- Generator vs iterator
- Generator sebagai saluran pipa
- Hasil dari konsep
- Perutean data generator (multiplexing, penyiaran)
- Contoh tracing generator
- Generator alat standar
- kesimpulan
- pro
- Minus
- literatur
- Hasil
Pengenalan umum
Python, , , . โ . , , , Python. , , , . "Fluent Python", , , .
, , . โ , , .
: .
โ?โ. , , . , .
โ?โ. , , , , , . .
- โ โ โ โ.
โ / / . , โ โ. , C, , .
- () , , . : , , , . , C#, Java.
โ , ( ). , โ Haskell, Lisp.
, . , , , , .
- (First Class Object).
, , โ , .. - . , .
- (lists, Lisp โ LISt Processing). .
- (High Order Functions). โ , .
. Python , map. Iterable , Iterable Iterator , .
- โโ (Pure Functions) โ .. ( : -).
Python . , .
- , , , .
, ( )
.
โ , . .
-
.
.
.
- , . , .
.
.
.
โ
.
, - , . โ , . , , , , , .
.
โ , . .
.
, , , . .
, , , . , , .
(closure)
โ . ยฉ Steve Majewski
โ , .
Python
Python โ map(), reduce(), filter() lambda. Python 1.x apply(), , . Python 2.0 . Python 2.3 , Python 3.0
, Python; , (if, elif, else, assert, try, except, finally, for, break, continue, while, def) , . , , , " Python" ( , Lisp'), , .
, , . if/elif/else Python
# Normal statement-based flow control
if <cond1>:
func1()
elif <cond2>:
func2()
else:
func3()
# Equivalent "short circuit" expression
(<cond1> and func1()) or (<cond2> and func2()) or (func3())
. skymorp, ,func1,func2()func3non falsy . , func , (func3) .
lambda
pr = lambda s:s
namenum = lambda x: (x==1 and pr("one")) or (x==2 and pr("two")) or (pr("other"))
assert namenum(1) == 'one'
assert namenum(2) == 'two'
assert namenum(3) == 'other'
, . for map().
for e in lst:
func(e) # statement-based loop
map(func,lst) # map-based loop
.
do_it = lambda f: f()
# let f1, f2, f3 (etc) be functions that perform actions
map(do_it, [f1,f2,f3])
while , .
# statement-based while loop
while <cond>:
<pre-suite>
if <break_condition>:
break
else:
<suite>
# FP-style recursive while loop
def while_block():
<pre-suite>
if <break_condition>:
return 1
else:
<suite>
return 0
while_FP = lambda: (<cond> and while_block()) or while_FP()
while_FP()
while while_block(), , (statements). (, , if/else ).
, ( while myvar == 7) , ( ) - ( while_block()). โ while_block() .
:
# imperative version of "echo()"
def echo_IMP():
while 1:
x = input("IMP -- ")
if x == 'quit':
break
else:
print(x)
echo_IMP()
# utility function for "identity with side-effect"
def monadic_print(x):
print(x)
return x
# FP version of "echo()"
echo_FP = lambda: monadic_print(input("FP -- ")) == 'quit' or echo_FP()
echo_FP()
. skymorp, ,input("IMP -- ") == 'quit'.
, , /, ( โ , ).
monadic_print(), , . , , monadic_print(x) , x.
, โ "?!". , , Python. (, , ) โ , , . , , - , .
, .
# Nested loop procedural style for finding big products
xs = (1,2,3,4)
ys = (10,15,3,22)
bigmuls = []
# ...more stuff...
for x in xs:
for y in ys:
# ...more stuff...
if x*y > 25:
bigmuls.append((x,y))
# ...more stuff...
# ...more stuff...
print(bigmuls)
, #...more stuff... โ , .
xs, ys, bigmuls, x, y . , , , .
, / , . (del) .
. , . :
bigmuls = lambda xs,ys: filter(lambda (x,y):x*y > 25, combine(xs,ys))
combine = lambda xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))
dupelms = lambda lst,n: reduce(lambda s,t:s+t, map(lambda l,n=n: [l]*n, lst))
print(bigmuls((1,2,3,4),(10,15,3,22)))
, . - ( ) . , , . โ โ ( ) :
print([(x,y) for x in (1,2,3,4) for y in (10,15,3,22) if x*y > 25])
, , list, tuple, set, dict comprehensions generator expressions โ , , , -
Xoltar Toolkit
, Python 2, . Xoltar Toolkit (Bryn Keller) .
Python. functional, Xoltar Toolkit lazy, , " ". , Xoltar Toolkit , Haskell.
Python , . , , . , .
>>> car = lambda lst: lst[0]
>>> cdr = lambda lst: lst[1:]
>>> sum2 = lambda lst: car(lst)+car(cdr(lst))
>>> sum2(range(10))
1
>>> car = lambda lst: lst[2]
>>> sum2(range(10))
5
, sum2(range(10)) , , .
, functional Bindings, .
>>> from functional import *
>>> let = Bindings()
>>> let.car = lambda lst: lst[0]
>>> let.car = lambda lst: lst[2]
Traceback (innermost last):
File "<stdin>",
line 1, in ? File "d:\tools\functional.py",
line 976, in __setattr__ raise BindingError, "Binding '%s' cannot be modified." % name
functional.BindingError: Binding 'car' cannot be modified. >>> car(range(10)) 0
, BindingError, .
returns
, , , Python. maybe
from returns.maybe import Maybe, maybe
@maybe # decorator to convert existing Optional[int] to Maybe[int]
def bad_function() -> Optional[int]:
...
maybe_number: Maybe[float] = bad_function().map(
lambda number: number / 2,
)
# => Maybe will return Some[float] only if there's a non-None value
# Otherwise, will return Nothing
, :
# Imperative style
user: Optional[User]
discount_program: Optional['DiscountProgram'] = None
if user is not None:
balance = user.get_balance()
if balance is not None:
credit = balance.credit_amount()
if credit is not None and credit > 0:
discount_program = choose_discount(credit)
# same with returns
user: Optional[User]
# Type hint here is optional, it only helps the reader here:
discount_program: Maybe['DiscountProgram'] = Maybe.from_value(
user,
).map( # This won't be called if `user is None`
lambda real_user: real_user.get_balance(),
).map( # This won't be called if `real_user.get_balance()` returns None
lambda balance: balance.credit_amount(),
).map( # And so on!
lambda credit: choose_discount(credit) if credit > 0 else None,
)
, ,
# Imperative style
def fetch_user_profile(user_id: int) -> 'UserProfile':
"""Fetches UserProfile dict from foreign API."""
response = requests.get('/api/users/{0}'.format(user_id))
# What if we try to find user that does not exist?
# Or network will go down? Or the server will return 500?
# In this case the next line will fail with an exception.
# We need to handle all possible errors in this function
# and do not return corrupt data to consumers.
response.raise_for_status()
# What if we have received invalid JSON?
# Next line will raise an exception!
return response.json()
, returns
import requests
from returns.result import Result, safe
from returns.pipeline import flow
from returns.pointfree import bind
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
"""Fetches `UserProfile` TypedDict from foreign API."""
return flow(
user_id,
_make_request,
bind(_parse_json),
)
@safe
def _make_request(user_id: int) -> requests.Response:
response = requests.get('/api/users/{0}'.format(user_id))
response.raise_for_status()
return response
@safe
def _parse_json(response: requests.Response) -> 'UserProfile':
return response.json()
, : , , , , .
, , @safe. Success [YourType] Failure [Exception]. !
, .
. , , returns , - , .
- https://devpractice.ru/fp-python-part1-general/
- Python
- https://degoes.net/articles/fp-glossary
- https://returns.readthedocs.io/en/latest/
โ . , .
>>> for x in [1,4,5,10]:
... print(x, end=' ')
...
1 4 5 10
, , ( ). , โ
>>> items = [1, 4, 5]
>>> it = iter(items)
>>> it.__next__()
1
>>> it.__next__()
4
>>> it.__next__()
5
>>> it.__next__()
.
for x in obj:
# statements
:
_iter = iter(obj) # Get iterator object
while 1:
try:
x = _iter.__next__() # Get next item
except StopIteration: # No more items
break
# statements
, , iter() . , __iter__() __next__().
, , :
>>> for x in Countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
:
class Countdown(object):
def __init__(self,start):
self.start = start
def __iter__(self):
return CountdownIter(self.start)
class CountdownIter(object):
def __init__(self, count):
self.count = count
def __next__(self):
if self.count <= 0:
raise StopIteration
r = self.count
self.count -= 1
return r
โ ,
def countdown(n):
while n > 0:
yield n
n -= 1
, , ( yield). -. .
def countdown(n):
print("Counting down from", n)
while n > 0:
yield n
n -= 1
>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>>
__next__().
>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>
yield , . __next__(). StopIteration.
>>> x.__next__()
9
>>> x.__next__()
8
>>>
...
>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
:
- โ
- (
__next__,__iter__. .), ..yieldPython .
>>> def x():
... return 1
...
>>> def y():
... yield 1
...
>>> [i for i in dir(y()) if i not in dir(x())]
['__del__', '__iter__', '__name__', '__next__', '__qualname__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
, generator object.
>>> a = [1,2,3,4]
>>> b = (2*x for x in a)
>>> b
<generator object at 0x58760>
>>> for i in b: print(b, end=' ')
...
2 4 6 8
(expression for i in s if condition)
# the same with
for i in s:
if condition:
yield expression
vs
, . โ . , , . , , ( , )
, . , :
, , - Apache. ,
:
81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238
:
bytes_sent = line.rsplit(None,1)[1]
.
81.107.39.38 - ... "GET /ply/ HTTP/1.1" 304 -
if bytes_sent != '-':
bytes_sent = int(bytes_sent)
,
with open("access-log") as wwwlog:
total = 0
for line in wwwlog:
bytes_sent = line.rsplit(None,1)[1]
if bytes_sent != '-':
total += int(bytes_sent)
print("Total", total)
. . , .
with open("access-log") as wwwlog:
bytecolumn = (line.rsplit(None,1)[1] for line in wwwlog)
bytes_sent = (int(x) for x in bytecolumn if x != '-')
print("Total", sum(bytes_sent))
, ,
, . , , . .
, . 1.3 18.6 , 16,7 .
AWK , 70.5
awk '{ total += $NF } END { print total }' big-access-log
:
- , 10% ,
- , ,
- , ,
. , ? , , .
yield from
'yield from'
def countdown(n):
while n > 0:
yield n
n -= 1
def countup(stop):
n = 1
while n < stop:
yield n
n += 1
def up_and_down(n):
yield from countup(n)
yield from countdown(n)
>>> for x in up_and_down(3):
... print(x)
...
1
2
3
2
1
>>>
, python (3.5 ) yield from await, await , .. await โ , . yield from โ await.
(, )
โ ,
, ( ) ( ). , .
# same with `tail -f`
def follow(thefile):
thefile.seek(0, os.SEEK_END) # End-of-file
while True:
line = thefile.readline()
if not line:
time.sleep(0.1) # Sleep briefly
continue
yield line
def gen_cat(sources):
#
for src in sources:
yield from src
def genfrom_queue(thequeue):
while True:
item = thequeue.get()
if item is StopIteration:
break
yield item
def sendto_queue(source, thequeue):
for item in source:
thequeue.put(item)
thequeue.put(StopIteration)
def multiplex(sources):
in_q = queue.Queue()
consumers = []
for src in sources:
thr = threading.Thread(target=sendto_queue, args=(src, in_q))
thr.start()
consumers.append(genfrom_queue(in_q))
return gen_cat(consumers)
def broadcast(source, consumers):
for item in source:
for c in consumers:
c.send(item)
class Consumer(object):
def send(self,item):
print(self, "got", item)
if __name__ == '__main__':
c1 = Consumer()
c2 = Consumer()
c3 = Consumer()
log1 = follow(open("foo/access-log"))
log2 = follow(open("bar/access-log"))
log3 = follow(open("baz/access-log"))
lines = multiplex([log1, log2, log3])
broadcast(lines,[c1,c2,c3])
โ , .
, , , , .
def trace(source):
for item in source:
print(item)
yield item
lines = follow(open("access-log"))
log = trace(apache_log(lines))
r404 = trace(r for r in log if r['status'] == 404)
โ , , , ,
. 3.0 . , pathlib.Path.rglob, glob.iglob, os.walk, range, map, filter. โ itertools.
:
- โ
- ,
- ,
- ,
- (, , )
- , ,
- , .
- https://www.dabeaz.com/generators/
- https://www.dabeaz.com/generators/Generators.pdf ( )
- https://wiki.python.org/moin/Generators (. Links)
- https://www.oreilly.com/library/view/fluent-python/9781491946237/
, . , Python, , - Python . , , .
, , . , โ , Python.
Tugas utama kita adalah menulis kode yang jelas, dapat dimengerti, indah, dapat diuji dan memilih alat yang tepat untuk ini. FP bukanlah tujuan itu sendiri, tetapi hanya sarana, seperti biasa, untuk dapat menulis kode yang lebih baik!
Jika Anda menemukan kesalahan, tulis di telegram Niccolumatau email lastsal@mail.ru. Saya akan senang menerima kritik yang membangun.