Basic Tips - Intermediate Tips - Advanced Tips

## Basic Tips ### 1) Learn how to iterate properly - Use ```for index, item in enumerate(list):``` to loop over the ```index``` of an item and the ```item``` at the same time. - Use ```for key, value in dictn.items():``` instead of iterating over ```keys``` and then getting values as ```dictn[key]```. - Use ```for item1, item2 in zip(list1, list2):``` to iterate over two lists at a time.
### 2) Use ```with``` statements to automatically ```close``` files Instead of calling ```close()``` manually when you finish accessing a file, consider using a ```with``` statement. It's shorter, more readable, and less error-prone, as it will take care of closing the file automatically for you.
Instead of doing this... try this:
```python file = open(file_path, 'w') file.write('Hello, World!') file.close() ``` ```python with open(file_path, 'w') as file: file.write('Hello, World!') # The file is closed automatically. ```

### 3) Use ```f-strings``` for formatting An ```f-string``` is preceded by the letter ```f``` and allows for inserting variables between curly braces ```{}```. Using ```f-strings``` is generally faster and makes your code more readable.
Instead of doing this... try this:
```python name = input('Enter your name: ') surname = input('Enter your surname: ') print('Hello, ' + name + ' ' + surname) ``` ```python name = input('Enter your name: ') surname = input('Enter your surname: ') print(f'Hello, {name} {surname}') ```

### 4) Learn to use ```split()``` and ```join()``` If you want to separate a string into a list of substrings, use ```lst = text.split(separator)```. Use ```text = separator.join(lst)``` to merge a list of strings together.
Instead of doing this... try this:
```python names = ['Olivia', 'Nicholas', 'Violet'] text = '' for name in names: text += name + ', ' text = text[:-2] print(text) >> 'Olivia, Nicholas, Violet' ``` ```python names = ['Olivia', 'Nicholas', 'Violet'] text = ', '.join(names) print(text) >> 'Olivia, Nicholas, Violet' ```

### 5) Transform ```list``` into ```set``` to remove duplicates Instead of iterating over a ```list``` to remove duplicate elements, take advantage of the properties of certain data structures, like the ```set```, which can only contain distinct elements. Transform your ```list``` into a ```set``` to remove duplicate elements. You can transform it back to a ```list``` afterwards if you need to.
Instead of doing this... try this:
```python cities = ['London', 'Paris', 'London'] unique_cities = [] for city in cities: if city not in unique_cities: unique_cities.append(city) print(unique_cities) >> ['London', 'Paris'] ``` ```python cities = ['London', 'Paris', 'London'] unique_cities = list(set(cities)) print(unique_cities) >> ['London', 'Paris'] ```

## Intermediate Tips ### 1) Instead of ```if variable == a or variable == b``` use ```if variable in (a, b)``` Instead of repeating a variable in different conditions of an ```if``` statement, check for belonging against a tuple of allowed values. This is shorter, less prone to error, and makes it easier to add or remove allowed values in the future. You can also use ```if min <= variable <= max``` or ```if variable in range(min, max + 1)``` to check if a variable is within a range.
Instead of doing this... try this:
```python if variable == a or variable == b: res = do_something(variable) ``` ```python if variable in (a, b): res = do_something(variable) ```

### 2) Learn how to unpack tuples You probably already know you can unpack tuples like this ```first = tuple[0]``` and ```second = tuple[1]```. But did you know you can also do ```first, second = tuple```? If the tuple has more than two elements, you can use ```first, second, *rest = tuple``` to unpack the first two elements and assign the rest to a list called ```rest```.
Instead of doing this... try this:
```python runners = 'John Mike Greg Luke Bob' positions = runners.split(' ') first = positions[0] second = positions[1] rest = positions[2:] ``` ```python runners = 'John Mike Greg Luke Bob' first, second, *rest = runners.split(' ') print(first) >> 'John' print(second) >> 'Mike' print(rest) >> ['Greg', 'Luke', 'Bob'] ```

### 3) Split long and difficult to read statements into multiple lines You can encase a statement in parentheses ```()``` and split it into multiple lines. This is useful for long statements that are difficult to read.
Instead of doing this... try this:
```python ... return Popen(...).stdout.read().decode()... ``` ```python ... return (Popen(cmd, shell=True, stdout=PIPE) .stdout .read() .decode() .strip()) ```

### 4) Instead of asking for permission, ask for forgiveness In Python, it's much more common to ```try:``` to do something and catch all possible exceptions, than to control input and output values using ```if``` statements. More likely than not, the function already does the necessary value checks from the inside and raises all possible errors. The only thing left for you to do is to catch them and define what should happen in case of error inside an ```except Exception:``` clause. This is why, instead of checking if a ```file``` exists and if you have permissions before opening it, you should just try to open it.
Instead of doing this... try this:
```python if not os.path.isfile(filename): print('File does not exist.') if not os.access(filename, os.W_OK): print('You dont have write permission.') with open(filename, 'w') as file: file.write('Hello, World!') ``` ```python try: with open(filename, 'w') as file: file.write('Hello, World!') except FileNotFoundError: print('File does not exist.') except PermissionError: print('You dont have write permission.') except OSError as exc: print(f'An OSError has occurred:\n{exc}') ```

### 5) Use ```center``` to center strings and ```os.get_terminal_size().columns``` to center to terminal. Learn how to use ```text.ljust(length)``` to align text to the left, filling it with spaces until it reaches a desired ```length```. Use ```text.rjust(length)``` or ```text.center(length)``` to align text to the right or center respectively. Set ```length = os.get_terminal_size().columns``` to center text relative to your terminal's width.
Instead of doing this... try this:
```python text = 'Hello, World' length = 20 text = ' '*((length//2) - (len(text)//2)) \ + text \ + ' '*((length//2) - (len(text)//2)) print(text) >> ' Hello, World ' ``` ```python text = 'Hello, World' length = 20 text = text.center(length) print(text) >> ' Hello, World ' ```

## Advanced Tips ### 1) Learn to transform for loops into comprehensions It's usually better to use comprehensions over for loops in Python when possible, as they are usually faster and shorter. However, it can be difficult to transform long and complex for loops into comprehensions. If you are iterating over ```items```, processing them in some way, and appending the results to a list to return it afterwards, try doing this instead: First, extract the body of the loop to a separate function that processes one ```item``` at a time. Let's call it ```process_item()```. Then, build your list comprehension by calling ```process_item()``` over all the ```items```.
Instead of doing this... try this:
```python items = [item1, item2, item3, item4...] new_items = [] for item in items: item = do_something(item) # Extract item = do_smth_else(item) # this ... # logic. new_items.append(item) ``` ```python items = [item1, item2, item3, item4...] def process_item(item): # 'process_item' item = do_something(item) # now does the item = do_smth_else(item) # processing ... # for a single return item # item. new_items = [process_item(item) for item in items] ```

### 2) Write readable comprehensions List, set, dictionary, and generator comprehensions may generally be shorter and run faster, but they can also be much harder to read. This is why, instead of writing your comprehensions in a single line, try splitting them into multiple lines. Notice how each keyword (```for```, ```in```, ```if```...) starts a new line. This makes the comprehension more comfortable to both read and modify if you need to add new conditions in the future.
Instead of doing this...
```python comments = {line_idx: line.strip() for line_idx, line in enumerate(file) if line.startswith('#')} ```
try this:
```python comments = {line_idx: line.strip() for line_idx, line in enumerate(file) if line.startswith('#')} ```

### 3) Don't iterate if you don't have to Iterating is expensive in Python and most common operations that require iteration can be done through functions that are either built-in or available in popular libraries/modules like ```functools```, ```itertools```, and ```numpy```. The underlying code for these functions is usually written in C and they are highly optimized. Use them whenever possible and iterate only when strictly necessary.
Instead of doing this... try this:
```python numbers = [1, 3, 4, 5, 7, 9, 2] total = 0 for number in numbers: total += number avg = total / len(numbers) ``` ```python numbers = [1, 3, 4, 5, 7, 9, 2] avg = sum(numbers) / len(numbers) ```

### 4) Use ```multiprocessing.Pool``` and get easy speedups with multiple processes Your processor has multiple cores that can execute several tasks at the same time, but by default your code usually runs on a single core. However, Python provides an easy way to make use of multiple processes to achieve parallelism and speedups that can go from x4 to x16 and even more for some machines.
Instead of doing this... try this:
```python requests = [req1, req2...] results = [] for request in requests: res = process_request(request) results.append(res) # Runs in 1m 22s, depending on HW. ``` ```python from multiprocessing import Pool requests = [req1, req2...] with Pool as p: results = p.map(process_request, requests) # Runs in 16s, depending on HW. ```

### 5) Use ```next(item for item in items if [condition])``` to get the first item that matches a condition It's a common task to get the first element of a list that has some specific property. The ```next()``` function can help you do that.
Instead of doing this... try this:
```python numbers = [1, 3, 4, 5, 7, 9, 2] result = None for number in numbers: if number > 3: result = number break print(result) >> 4 ``` ```python numbers = [1, 3, 4, 5, 7, 9, 2] result = next((number for number in numbers if number > 3), None) # Fallback value. print(result) >> 4 ```