### Simple Tricks to Level Up Your Python Coding ### List Comprehensions

A handy feature in Python is the list comprehension, which we can construct a list very conveniently with. The list comprehension has a general format of [some_expression for element in iterable if some_condition].

``````
>>> a = [1, 2, 3, 4, 5]
>>> [x*2 for x in a]
[2, 4, 6, 8, 10]
>>> [x*3 for x in a if x%2 == 1]
[3, 9, 15]

``````

### Set Comprehensions

The usage of the set comprehension is similar to the list comprehension, as above. The difference is that we’ll use the curly brackets instead of square brackets. Also, the duplicate elements will get removed by the definition of the set data type.

``````
>>> a = [1, -2, 2, -3, 3, 4, 4, 5, 5, 5]
>>> {x*x for x in a}
{1, 4, 9, 16, 25}
``````

### Dict Comprehensions

Besides the list and set comprehensions, the comprehension feature is also available to the creation of the dictionary data type. A dict consists of key-value pairs, so the dict comprehension involves the specification of keys and values, which are separated by a colon.

``````
>>> a = [1, 2, 3, 4, 5]
>>> {x: x*x for x in a}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
``````

### Generator Expression

Generators in Python are a convenient way to create iterators. As generators are “lazy” (i.e., yield the needed item when requested), they’re very memory-efficient. One particular way to create generators is called generator expression, which is syntactically similar to the list comprehension, except for using parentheses instead of square brackets.

In the below examples, the parentheses are optional when generators are directly used in functions that can take iterables.

``````
>>> sum(x**2 for x in range(100))
328350
>>> max((x*x for x in range(100)))
9801
``````

### Unpack a Tuple

Tuples are a very common data structure in Python. They’re just groups of related values, and common usage of tuples involves accessing their elements. We can access these elements using indices, but unpacking is a more convenient way. Related to its usage, we can use an underscore to indicate the elements that we don’t need and use an asterisk to assign the remaining elements other than the named ones.

``````
>>> items = (0, 'b', 'one', 10, 11, 'zero')
>>> a, b, c, d, e, f = items
>>> print(f)
zero
>>> a, *b, c = items
>>> print(b)
['b', 'one', 10, 11]
>>> *_, a, b = items
>>> print(a)
11
``````

### Use Enumerate() In for Loops

The enumerate() function takes in an iterable to create an iterator. Besides, it can track the number of iterations. We can optionally set the start of the counting. The default counting starts at 0.

``````
>>> students = ('John', 'Mary', 'Mike')
>>> for i, student in enumerate(students):
...     print(f'Iteration: {i}, Student: {student}')
...
Iteration: 0, Student: John
Iteration: 1, Student: Mary
Iteration: 2, Student: Mike
>>> for i, student in enumerate(students, 35001):
...     print(f'Student Name: {student}, Student ID #: {i}')
...
Student Name: John, Student ID #: 35001
Student Name: Mary, Student ID #: 35002
Student Name: Mike, Student ID #: 35003
``````

### Use Reversed() In for Loops

The reversed() function is often used in the for loops as a way to create an iterator in the reversed order of the original iterable.

``````
>>> tasks = ['laundry', 'picking up kids', 'gardening', 'cooking']
...
cooking
gardening
picking up kids
laundry

``````

### The Zip() Function

The zip() function is useful to join multiple iterables on a one-to-one match basis. If certain iterables exceed the shortest one, they get truncated. This function returns an iterator, and thus it is frequently used in an iteration. We can also use the zip() function to unzip an iterator using the asterisk sign and assign the unzipped items to variables.

``````
>>> students = ('John', 'Mary', 'Mike')
>>> ages = (15, 17, 16)
>>> scores = (90, 88, 82, 17, 14)
>>> for student, age, score in zip(students, ages, scores):
...     print(f'{student}, age: {age}, score: {score}')
...
John, age: 15, score: 90
Mary, age: 17, score: 88
Mike, age: 16, score: 82
>>> zipped = zip(students, ages, scores)
>>> a, b, c = zip(*zipped)
>>> print(b)
(15, 17, 16)
``````

### Lambdas for Sorting

Lambdas are anonymous functions that can take multiple arguments with a single-line expression. One of its common usages is to set as the key argument in the sorted() function. Besides this, lambdas are often used in some functions (e.g., max(), map()) where a one-line expression is applicable to replace a regular function using the def keyword.

``````
>>> students = [{'name': 'John', 'score': 98}, {'name': 'Mike', 'score': 94}, {'name': 'Jennifer', 'score': 99}]
>>> sorted(students, key=lambda x: x['score'])
[{'name': 'Mike', 'score': 94}, {'name': 'John', 'score': 98}, {'name': 'Jennifer', 'score': 99}]
``````

### Shorthand Conditional Assignment

This feature is mostly a syntax sugar. When you need to assign a value to a variable based on a certain condition, we can use a shorthand assignment using this general form: y = x if condition_met else another_x.

``````
>>> some_condition = True
>>> # the expanded format
>>> if some_condition:
...     x = 5
... else:
...     x = 3
>>> print(f'x is {x}')
x is 5
>>> # the shorthand way
>>> x = 5 if some_condition else 3
>>> print(f'x is {x}')
x is 5
``````

### Get the Key Whose Value Is Maximal in a Dictionary

For a dictionary, we sometimes need to find out the key whose value is maximal. We can first find out the maximal value’s index in a list of all the values, and then find the corresponding key from another list that stores all the keys. Alternatively, an easier way is to specify the key argument in the max() function.

For simplicity, we don’t consider scenarios where the maximal values may have duplicates. Besides, the same approach can be applied to finding the key with minimal value using the min() function.

``````
>>> model_scores = {'model_a': 100, 'model_z': 198, 'model_t': 150}
>>> # workaround
>>> keys, values = list(model_scores.keys()), list(model_scores.values())
>>> keys[values.index(max(values))]
'model_z'
>>> # one-line
>>> max(model_scores, key=model_scores.get)
'model_z'
``````

### Walrus Operator

The walrus operator (:=) is a new feature available in Python 3.8+. It’s just another name for assignment expression — assignment to a variable within an expression. Usually, when an expression uses a variable, the variable has to be declared earlier. With the walrus operator, the variable assignment can be included in the expression, and the variable is available to use right away.

``````
>>> a = ['j', 'a', 'k', 'd', 'c']
>>> if (n := len(a))%2 == 1:
...     print(f'The number of letters is {n}, which is odd.')
...
The number of letters is 5, which is odd.
``````

### The Map() Function

The map() function is a high-order function (i.e., a function that uses a function as an argument or returns a function as its output). It has a general format of map(function, iterables), which will apply the function to the iterable(s) and return a map object, which is an iterator. The number of iterables should match the number of needed arguments for the function.

In the example below, the built-in pow() function expects two arguments. Certainly, a custom function can be used as well. As a side note, when we use the map() function to create a list, we can probably use the list comprehensions to achieve the same effect.

``````
>>> numbers = (1, 2, 4, 6)
>>> indices = (2, 1, 0.5, 2)
>>> # use map()
>>> list(map(pow, numbers, indices))
[1, 2, 2.0, 36]
>>> # list comprehensions
>>> [pow(x, y) for x, y in zip(numbers, indices)]
[1, 2, 2.0, 36]
``````

### The Filter() Function

The filter() function is to filter a sequence using the specified function or lambda function. This function returns a filter object, which is an iterator. Overall, its usage is very similar to the map() function.

``````
>>> def good_word(x: str):
...     has_vowels = not set('aeiou').isdisjoint(x.lower())
...     long_enough = len(x) > 7
...     good_start = x.lower().startswith('pre')
...     return has_vowels & long_enough & good_start
...
>>> words = ['Good', 'Presentation', 'preschool', 'prefix']
>>> list(filter(good_word, words))
['Presentation', 'preschool']
``````

### The Any() Function

Suppose we have a list of records tracking John’s arrival time to the work. One use case is that we want to know if he has any late arrival this week, in which case, the any() function is very handy. This function returns True if any of the elements in a boolean list is True.

``````
>>> arrival_hours = {'Mon': 8.5, 'Tue': 8.75, 'Wed': 9, 'Thu': 8.5, 'Fri': 8.5}
>>> arrival_checks = [x>8.75 for x in arrival_hours.values()]
>>> any(arrival_checks)
True
``````

### The All() Function

Following up on the same example above, we also want to know if he arrived at the work always before 9:30 for the whole week. To test if it’s the case, we can use the all() function, which returns True only if all of the elements in a boolean list are True.

``````
>>> arrival_checks_all = [x<9.5 for x in arrival_hours.values()]
>>> all(arrival_checks_all)
True
``````