Python函数式编程笔记。
Introducation
- Function Programming has a long history
- List 1958
- Renaissance: F#, Haskell, Erlang…
- Used in industry
- Trading
- Algorithmic
- Telecommunication(Concurrency)
Features Of Functional Programming
- Everything is a function
- Pure functions without side effects
- Immutable data structures
- Preserve state in functions
- Recursion instead of loops / iteration
Advantages of Functional Programming
- Absence of side effects can make your programs more robust
- Programs tend to be more modular come and typically in smaller building blocks
- Better testable - call with same parameters always return same result
- Focus on algorithms
- Conceptional fit with parallel / concurrent programming
- Live updates - Install new release while running
Disadvantages of Functional Programming
- Solutions to the same problem can look very different than procedural / object-oriented ones
- Find good developers can be hard
- Not equally useful for all types of problems
- Input/output are side effects and need special treatment
- Recursion is “an order of magnitude more complex” than loops/iteration
- Immutable data structures may increase run times
Python’s Functional Features - Overview
- Pure functions (sort of)
- Closures - hold state in functions
- Functions as object and decorators
- Immutable data types
- Lazy evaluation - generators
- List(dictionary, set) comprehensions
- functions, itertools, lambda, map, filter
- Recursion - try to avoid, recursion limit has a reason
Pure Functions - No Side Effects
- No side effect, return value only
- “Shallow copy” problem
1 | def dp_pure(data): |
- An ooverloaded * that modifies data or causes other side effects would make the function un-pure
- No guarantee of pureness
- Pure functions by convention
Side effects
- Side effects are common
1 | def do_side_effect(my_list): |
Functions are Objects
1 | def func1(): |
1 | >>>> my_funcs = {'a': func1, 'b': func2} |
- Everything is an object
Closures and “Currying”
1 | def outer(outer_arg): |
Partail Functions
- Module functools offers some tools for the Functional approach
1 | import functools |
Recursion
1 | def loop(n): |
Recursion - Time it in IPython
1 | %timeit loop(le3) |
- sys.setrecursionalimit(int(le6)) and %timeit recurse(le5) segfaulted my IPython kernel
Lambda
- Allow versy limited anonymous functions
- Expressions only, no statements
- Past discussion to exclude it from Python 3
- Useful for callbacks
1 | def use_callback(callback, arg): |
Lambda - Not Essential
- Always possible to add two extra lines
- Write a function with name and docstring
1 | def double(arg): |
List Comprehensions instead of map
- Typical use of map
1 | >>> map(lambda arg: arg * 2, range(2, 6)) |
- Replace with list comprehension
1 | [x * 2 for x in range(2, 6)] |
List Comprehensions instead of filter
- Typical use of filter
1 | >>> filter(lambda x: x > 10, range(5, 16)) |
- Replace with list comprehension
1 | >> [x for x in range(5, 16) if x > 10] |
Decorators
- Application of closures
1 | import functools |
Immutable Data Types - Tuples Instead of Lists
1 | my_list = range(10) |
- Contradicts the usage recommendation
- Lists == elements of the same kind
- Tuple == “named” elements
Immutable Data Types - Freeze Sets
1 | my_set = set(range(5)) |
- Can be used as dictionary keys
Not Only Functional
- Pure functional programs can be difficult to implement
- Combine with procedural and object-oriented program parts
- Choose right tool, for the task at hand
- Develop a feeling where a functional approach can be beneficial
Avoid Side effects
1 | class MyClass(object): |
- Set all attributes in init (Pylint will remind you)
- Actual useful application of static methods
- Fewer side effects than setting attributes outside init
- Your beloved classes and instances are still here
- Inheritance without overriding init and use super,child class implements own make_attr1()
Freeze Classes
1 | class Reader(object): |
- Mutable data structures are useful for reading data
- “Freeze” to get read-only version
- No future, unwanted modifications possible
Freeze Classes - One Liner Version
- Still kind of readable
1 | class Reader(object): |
Stepwise Freezing and Thawing I
1 | class FrozenUnFrozen(object): |
Stepwise Freezing and Thawing II
1 | >>> fuf = FrozenUnFrozen() |
Use Case for Freezing
- Legacy code: Where are data modified?
- Complex systems: Detect unwanted modifications
Immutable Data Structures - Counter Arguments
- Some algorithms maybe diffcult to implement
- Can be rather inefficient - repeated re-allocation of memory
- Antipattern string concatanation
1 | 'text' >> s += |
- Try this in Jypthon and (standrad-)PyPy
Lazy Evaluation
- Iterators and generators
1 | >> [ x * 2 for x in xrange(5)] |
- Saves memory and possibly CPU time
Itertools - “Lazy Programmers are Good Programmers”
- Module itertools offers tools for the work with iteratoes
1 | it.izip('abc', 'xyz') |
1 | list(it.islice(iter(range(10)), None, 8, 2)) |
Pipelining -Chaining Commands
- Generators make good pipelines
- Useful for workflow problems
- Example parsing of a log file
Generators - Pull
- Log file:
1 | 35 |
Generators - Pull - Import
1 | import sys |
Generators - Pull - Read File
1 | def read_forever(fobj): |
Generators - Pull - Filter Out Comment lines
1 | def filter_comments(lines): |
Generators - Pull - Convert Numbers
1 | def get_number(lines): |
Generators - Pull - Initialize the Process I
1 | def show_sum(file_name = 'oyr.txt'): |
Coroutines - Push
- Log file:
1
2
3
4
5
6
7Error: 78
DEBUG: 72
WAN: 99
CRITICAL: 97
Error: 78
Error: 89
Error: 46
Coroutines - Push -Initialize with a Decorator
1 | def init_coroutine(func): |
Coroutines - Push - Read the File
def read_forever(fobj, target):
counter = 0
while True:
line = fobj.readline()
if not line:
time.sleep(0.1)
continue
target.send(line)
Coroutines - Push - Filter Out Comments
1 |
|
Coroutines - Push - Convert Numbers
1 | @init_coroutine |
Coroutines - Push - Consumer I
1 |
|
Coroutines - Push - Consumer II
1 |
|
Coroutines - Push - All Consumers
1 | TARGETS = { |
Conroutines - Push - Initialize
1 | def show_sum(file_name='out.txt'): |
def show_sum(file_name=’out.txt’):
read_forever(open(file_name), filter_comments(get_number(TARGETS)))
if name == ‘main‘:
show_sum(sys.argv[1])
Conclusions
- Python offers useful functional features
- But it is no pure functional language
- For some tasks the functional approach works veru well
- For some others much less
- Combine and switch back and forth with oo and procedural style
& “Stay pythonic, be pragmatic”