We write in Python immediately well

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 . , ,





  1. , -





  2. v



    b



    , ,





  3. - ( ),





? . :





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> :)








All Articles