Inefficient software isn't gross. What's gross is a language that makes programmers do needless work. Wasting programmer time is the true inefficiency, not wasting machine time. -Paul Graham
Link To The Talk: https://tinyurl.com/rsmas-python1
Clean code is
- Functional
- Completes the task at hand
- Works in a reasonably "normal" world where "normal" is defined by the use case
- Readable
- The code is easily understandable by the original developer
- The code is easily understood by the current and future team
- Reusable
- Code written in one part of the program can be reused in other parts of the program
- Work done today will benefit the team in the future
- Writing Clean, Pythonic Code
- Idiomatic Python
- Structuring Projects
- Especially conventions laid out by the language
- Add on common patterns to standard pattern to a group-specific style guide
- Google Style Guide
- PEP 8 - The official Python Style Guide
- Use Linters
pylint
andpycodestyle
- Python Linters In Practice
# Nope
f = lambda x: 2*x
# Yup
def double(x): return 2*x
# From PEP 8: Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier.
- Understanding your code's logic shouldn't be challenging.
- In any language, there are cool features. Don't use them.
- Use Case: List Comprehensions
# GOOD!
squares = [x * x for x in range(10)]
# Not so easy to understand...
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
# Better
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
- Keep the campground cleaner than you found it.
- Always think about Future You and Future Team
- Make it really easy on your future self to implement new features, reason about the codebase, etc.
- This means writing code that is reusable eliminating waste
- Choose descriptive and unambiguous names.
variable_name
function_name
ClassName
- Make meaningful distinction.
- Use pronounceable names.
- Use searchable names.
- Replace magic numbers with named constants.
- Optimize for reusability
- If you think you'll reuse something, make it into a function
- Small and do one thing.
- Use descriptive names and arguments
- We will revisit you in a second
- Prefer fewer arguments.
- Have no side effects.
- We will revisit you in a second
# Not so great
def ps(upper):
if upper == None:
upper = float('inf')
ps = [2]
curr = ps[-1]
while curr < upper:
if any(map(lambda p: curr % p == 0, ps)):
ps.append(curr)
yield curr
curr += 1
# Better
def primes(upper_limit = None) -> Generator[int, None, None]:
"""
A generator that yields prime numbers starting with 2
:upper_limit: Optional parameter for a maximum prime number to yield
:return: Generator[int, None, None]
"""
if not upper_limit:
upper_limit = float('inf')
primes = []
current = 2
while current < upper_limit:
# Lazily evaluates divisibility
divisible_by_primes = map(lambda prime: current % prime == 0, primes)
if not any(divisible_by_primes):
primes.append(current)
yield current
current += 1
- A Side Effect is a change some sort of state
- For functions, this is a change of state that isn't part of the
return
value - Examples of changing state
- Changing the value of a global variable
- Changing the data of an input argument (appending to an input list)
- Writing to stdout (
print
)
- It becomes easier to reason about code when we write functions that are Side Effect-free
# Side Effect Free!
def sum_of_list1(values: List[int]) -> int:
total = sum(values)
return total
# Side Effects Alert!
def sum_of_list2(values: List[int]) -> int:
total = sum(values)
values.append(total) # I'm not Pure!
return total
# Side Effects Alert!
def sum_of_list3(values: List[int]) -> int:
total = sum(values)
print("Sum:", total)
return total
- Positional Arguments
- Required to call a function
- Keyword Arguments
- Optional arguments for a function
- Positional Arguments can be called using keywords
- This can make your code more readable
# A non-descript function call
twitter_search('@obama', False, 20, True)
# So much easier to understand
twitter_search('@obama', retweets=False, numtweets=20, popular=True)
- Keyword arguments are evaluated once. This can lead to some weird effects
- To get around some weird effects, use a default parameter of
None
rather than a data structure
# Buggy!
def append_to(element, to=[]):
to.append(element)
return to
>>> my_list = append_to(12)
[12]
>>> my_other_list = append_to(42)
[12, 42]
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to
# Note: What's wrong about this example in general?
>>> 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.
Readability counts.
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.
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.
colors = ['red', 'green', 'blue', 'yellow']
# Bad
for i in range(len(colors)):
print i, '--->', colors[i]
# Better
for i, color in enumerate(colors):
print i, '--->', color
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']
# Bad
n = min(len(names), len(colors))
for i in range(n):
print names[i], '--->', colors[i]
# Better
for name, color in zip(names, colors):
print name, '--->', color
# Not so elegant
colors = ['red', 'green', 'red', 'blue', 'green', 'red']
# Simple, basic way to count. A good start for beginners.
d = {}
for color in colors:
if color not in d:
d[color] = 0
d[color] += 1
# {'blue': 1, 'green': 2, 'red': 3}
# Better
d = {}
for color in colors:
d[color] = d.get(color, 0) + 1
# Best
from collections import defaultdict
d = defaultdict(int)
for color in colors:
d[color] += 1
.
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
# In this example, we're grouping by name length
# Meh
d = {}
for name in names:
key = len(name)
if key not in d:
d[key] = []
d[key].append(name)
# {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}
# Better
d = {}
for name in names:
key = len(name)
d.setdefault(key, []).append(name)
# Best
d = defaultdict(list)
for name in names:
key = len(name)
d[key].append(name)
- Delays computation until you a values is needed
map
,filter
, and all ofitertools
are powerful for writing clean and efficient code
- You can pack and unpack tuples with commas
- We have already been doing that when dealing with
enumerate
andzip
# Bad
def fibonacci(n):
x = 0
y = 1
for i in range(n):
print x
t = y
y = x + y
x = t
# Better
def fibonacci(n):
x, y = 0, 1
for i in range(n):
print x
x, y = y, x + y
- Nested unpacking works too and more elegant unpacking
a, (b, c) = 1, (2, 3)
a, *rest = [1, 2, 3]
# a = 1, rest = [2, 3]
a, *middle, c = [1, 2, 3, 4]
# a = 1, middle = [2, 3], c = 4
- Ignoring arguments too
fname, __, lname = ("David", "Welin", "Grossman")
- When writing Python modules, all code should be in a function
- Use
if __name__ == "__main__"
to allow the script to run as a standalone program too
from that import this
import this_other_thing
def function1(arg1, arg2):
return arg1 * arg2
# ...
if __name__ == "__main__":
# Run code when the script is executed as a standalone function
pass
``
### Typing
1. Python doesn't have declared types. However, you can optionally use types
1. Types can make the code more readable and can catch errors before execution
```python
from typing import List
def greeting(name: str) -> str:
return 'Hello ' + name
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
- Use linters
- They will create a syntax and coding standard
- They will pick up bugs before runtime
- Optimize for Readability and Reusability
- Eat your vegetables today so that tomorrow you will be fit and strong. And have a fantastic code base!!
- Modular code
- Rewrite everything into functions
- Break problems down into their fundamental steps and work up from there
- Python Code Base Structure
- Logging Events in Code
- Good software practices for working in a team
- A closer look at some RSMAS code