Hello Habr!
Today I will take off the animator's costume and instead of entertainment, I will tell you a little about the python.
I'm a pretty mediocre programmer, but sometimes I manage to put something off my guard, and I am considered a senior. And somehow it so happened that I began to do a lot of code reviews. Looking through file after file, I suddenly saw that people and projects were changing, but the moments that I, such a bore, find fault with, remain the same. Therefore, I decided to collect the most common patterns in this chaotic article and I hope that they will help you write cleaner and more efficient python code.
Early quit
This is definitely in the first place, because everywhere, in everyone, I see it:
def foo(a: bool):
if a:
#
# ... 50 LOCs
#
return True
else: # if not a
return False
When we just write / read the code if a:
, we remember that somewhere there, at the end, we need to consider the else
case. If the code inside is if a
large, then else
it will generally be "torn off" from the context.
We could swap the conditions in places:
def foo(a: bool):
if not a:
return False
else:
#
# ... 50 LOCs
#
return True
This is easier to read, since not a
we have already considered the case and threw it out of my head at the very beginning. But if you look closely, you else
don't need it at all:
def foo(a: bool):
if not a:
return False
# "not a"
#
# ... 50 LOCs
#
return True
This is the whole trick - when we write a function, we try to get out of it as soon as possible by cutting off some bad cases. This technique is great in that it allows you to get rid of nesting levels (that is, now there is none else
), and even the programmer's memory is unloaded, because there is no branching of logic.
- :
import requests
def scrape(url: str) -> dict:
response = requests.get(url)
if not response.ok: # -
return {}
# ,
data = response.json()
if 'error' in data: # ,
raise ValueError(f'Bad response: {data["error"]}')
# ,
if 'result' not in data: # ,
return {}
# ,
# ... parse data['result'] ...
"", , , , .
, return
break
continue
.
One-line assignment
. :
if a:
v = 1
else:
v = 2
- if-else
, :
v = 1 if a else 2
if a == 1:
v = 10
elif a == 0:
v = 0
elif a == -1:
v = -10
elif a == -2:
v = - 20
, a not in {0, 1, -1, -2}
, - v
. , , v
, 4 , v
4 . , ,
, -
v
b
, ,
- ( ),
? . :
var = <some code>
. " ", , - , .
v = {
1: 10,
0: 0,
-1: -10,
-2: -20,
}[a]
v
, a
v
, a
, , . , [a]
.get(a, default_value)
.
Definition close to usage
. :
def foo(url: str):
fields = ['a', 'b', 'c']
response = requests.get(url)
response.raise_for_status()
data = response.json()
if 'result' not in data:
raise ValueError()
for field in fields:
print(data[field])
, - for field in fields
, , fields
, , . , PyCharm 2 - "jump to definition", "jump ", , .
: , . , : " "? , , , . , .
fields
, , for
:
for field in ['a', 'b', 'c']:
print(data[field])
Too many indents
, . , . , , , , .
for x in range(10):
result = foo(x)
if result:
second = bar(result)
if second:
print(second)
else:
# <<< HERE
print('not second')
else:
print('not good')
<<< HERE
, - x
0 9, result
True
, second
.
- , N
, , , . , - . "Early Quit", .
for x in range(10):
result = foo(x)
if not result:
print('not good')
continue
second = bar(result)
print_second(second)
, .. ,
for x in range(100):
for y in range(100):
for z in range(100):
if x % 10 == 0:
if y % 10 == 0:
print('haha')
itertools
,
from itertools import product
def print_(x: int, y: int, z: int):
if x % 10 == 0 and y % 10 == 0:
print('haha')
for x, y, z in product(range(100), range(100), range(100)):
print_(x, y, z)
Dangerous loops
! - - :
for i in range(100):
print(i)
, , . - while True
:
a = 0
while True:
a += 1
if a == 100:
break
- a = 0
a = 100
, . - , , , , . , - , , while True
- .
-, :
max_iterations = 100
a = 0
for i in range(max_iterations):
a += 1
if a == 100:
break
else:
raise ValueError('max_iterations reached')
- . - , . 20 , 20 , 400 - .
for x in range(20):
for y in range(20):
fn(x, y) # called 400 times
, , , - , :
for x, y in product(range(20), range(20)):
if 10 < x < 20 and 10 < y < 20:
fn(x, y)
Copy-paste more than twice
- - . -, - , . -, - PVS Studio
if v == 'a':
self.value_a = 1
elif v == 'b':
self.value_b = 1
elif v == 'c':
self.value_c = 1
- , - , getattr / setattr
, - . , - .
setattr(self, f'value_{v}', 1)
Interconnected lines of code
- , , , . , . , :
rows = [
{'col 1': 'value 1', 'col 2': 'value 2'},
{'col 1': 'value 3', 'col 2': 'value 4'},
]
writer = csv.DictWriter(..., fieldnames=['col 1', 'col 2'])
rows
, , fieldnames
. fieldnames
"" :
rows = [
{'col 1': 'value 1', 'col 2': 'value 2'},
{'col 1': 'value 3', 'col 2': 'value 4'},
]
writer = csv.DictWriter(..., fieldnames=rows[0].keys())
Type hints
: type hints. , , ( ), , . type hints .
def foo(processor):
# wtf is processor? what is returned?
...
def foo(processor: Processor) -> str:
# okay, now I can jump to definition of Processor and see what it is
...
" ", type hint Tuple[int, str, datetime]
, (object_id, name, creation_datetime)
. namedtuple
, .
Quick "in" check
: - -? set
(), element in set
. element in list
, , .
:
items = [instance1, instance2, instance3]
if instance4 in items:
print('yes')
:
items = {instance1.id, instance2.id, instance3.id}
if instance4.id in items:
print('yes')
Bulk
, . : , .
? , .
SQL ? , .
view ? , .
? , .
, . SQL - JOIN
INDEX
, Django select_related
, only
, values_list
, update
bulk_create
, - ThreadPoolExecutor
. , -, - , - , , . , , , .
Concurrency safety
, . . . , ? API? ?
, . , .
, , - . .
, , , threading.Lock
, @transaction.atomic
, SELECT FOR UPDATE
.
Asserts everywhere
- assert
. , , " , ", assert
- , , .
? :
result = get(service_url).json()
assert result in {'ok', 'fine', 'good'}
, Item
? :
items = Item.objects.all()
assert len(items)
print(items[0])
, ? :
result = foo(bar(baz(x)))
assert result is not None
General rule: if you are expecting something or are not sure about something, then bet assert
.
It is worth remembering, however, that they assert
can be disabled with the help of a spell -O
, but if you meet those who do this, then send them from me ardent greetings and a little bit of a beginner.
What is all this for
As you can see, the purpose of these rules is to improve the readability of the code, relieve the programmer's memory and reduce the number of errors. There is no rocket science here, all the points are quite simple, but if you stick to them, it will be easier for me if your code ever gets to me for review> :)