- Don't use classes for information. Use regular data structures (dict, list, tuple, set) and use functions to manipulate them. If you really want/need something like a struct, use a dataclass.
- The itertools module has a bunch of useful constructs for creating and combining iterators. Take advantage of Python's iterators and construct your own when necessary.
- List comprehensions and generator expressions are great for clear and concise data transformations
- You're going to have to deal with state somewhere in your program. That's fine, some state is necessary to do real work. Don't feel bad about creating classes for things like context managers (e.g. for a database connection) so you can use it in a with statement and it will handle the setup and teardown logic for you. That way your business logic stays concise and Pythonic since those classes can be in separate modules and imported as necessary. I've found this 'functions in the front, classes in the back' approach to writing Python useful as it lets me think about what my program does in a functional manner without denying myself the ergonomics of Python's class-based ecosystem.
One problem is many of these datastructures are easy to mutate. When I need to ensure immutability to a dictionary, for instance - quite often - I can't avoid wrapping it in a class and setting up an interface to lock down mutation.
If Python had a way to declare immutable datastructure, similarly to Clojure, it would make life a lot easier...
> Read-only proxy of a mapping. It provides a dynamic view on the mapping’s entries, which means that when the mapping changes, the view reflects these changes.
https://docs.python.org/3/library/types.html#types.MappingPr...
If function A passes a dict to function B, I would like:
1) Function A to keep the dict intact while function B can manipulate its content;
2) Function A can change the dict after passing to B, and B still keep the original copy, unaware of A's later changes.
One way of doing this is by deep copying dictionaries around, but it can easily become a big performance issue.
I've written a lot of functionalish Python, and I've found that this is a consistent way to breed code that's hard to understand. Instead, I would default to (usually frozen) dataclasses to represent data—they're functional records that also let your code reflect the concepts that make sense for whatever you're doing. A list of dictionaries of strings looks like any other list of dictionaries of strings; a sequence of User objects has clear semantics and, as a benefit, is harder to mutate in unexpected ways.
Use named parameters and bypass the builder pattern.
Store operations in objects/lists. Bypass the strategy patterns with this knowledge. Did you know myobject.mymethod is a function, and you can do x = myobject.mymethod, and then x()? Store methods in lists, pass them as parameters, partially-apply parameters to them.
Generators are more than what the iterator pattern will never be.
I covered a few core concepts (e.g., functions as first-class citizens, closures, partial application, etc...) and added a few real world examples of using a functional centric design. The text/format has some rough edges, but overall I think the text is useful for internalizing how to leverage a functional-ish approach.
Other resources.