good_practices.py (Source)

"""
Common Python Patterns to use
"""
def show_best_practices(pos1, pos2, /, pos_or_kwd=True, *, kwd1, kwd2=""):
    """
    Shows good and best practices for getting clean & idiomatic code.
    # https://deepsource.io/blog/python-positional-only-arguments/
    PEP 570
    :param pos1: 1st positional `only` argument
    :param pos2: 2nd positional `only` argument
    :param pos_or_kwd: after '/' -> positional `or` keyword args
    :param kwd1: after '*' -> keyword `only` argument
    :param kwd2: 2nd keyword `only` argument
    :return:
    """
    # === Using `Context Managers` for Files ===
    with open('abc.txt', 'r') as file_obj:
        """
        Context managers in Python help to facilitate proper handling of resources,
        to control what to do when objects are created or destroyed. This removes the
        overhead of handling the creation and deletion of objects.
        """
        data = file_obj.read()
        # do something exciting
    # === Using isinstance() to compare types ===
    name = 'Alex'
    if isinstance(name, str):
        """isinstance() is usually the preferred way to compare types. It’s not only
        faster but also considers inheritance, which is often the desired behavior. In
        Python, you typically want to check if a given object behaves like a string or a
        list, not necessarily if it’s exactly a string. So instead of checking for
        string
        and all its custom subclasses, you can just use `isinstance`."""
        print('It is a string')
    else:
        print('It is not a string')
    # === Use of `exec()` ONLY in very specific case ===
    def print_word():
        """The exec statement enables you to dynamically execute arbitrary Python code
        which is stored in literal strings. Building a complex string of Python code and
        then passing that code to exec results in code that is hard to read and hard to
        test. Anytime the use of exec error is encountered, you should go back to the
        code and check if there is a clearer, more direct way to accomplish the task."""
        print("Hello, World!")
    print_word()
    # === Use exception type(s) specified ===
    try:
        """Not specifying an exception type might not only hide the error but also leads
        to losing information about the error itself. So its better to handle the
        situation with the appropriate error rather than the generic exception otherwise
        as shown below.
        With this pattern, you are able to handle exceptions based on their actual
        exception-type. The first exception type that matches the current error is
        handled first. Thus, it is recommended to handle specific exception types
        first (
        e.g., ZeroDivisionError) and generic error types (e.g., Exception) towards the
        end of the try-except block.
        """
        print(5 / 0)
    except ZeroDivisionError as e:
        print(f"ZeroDivisionError: {e}")
    except Exception as e:
        print(F"Exception: {e}")
    else:
        print("No errors")
    finally:
        print('Good day')
    # === Using map() or filter() instead of list comprehension ===
    """For simple transformations that can be expressed as a list comprehension,
    it is better to use list comprehensions over map() or filter() as map and filter
    are used for expressions that are too long or complicated to express with a list
    comprehension."""
    values = [1, 2, 3]
    doubles = [num * 2 for num in values]
    # === Use generator expression directly, w/o unnecessary comprehension ===
    """Built-in functions like `all`, `any`, `enumerate`, `iter`, `itertools.cycle`,
    `itertools.accumulate`, can work directly with a generator expression. They do not
    require comprehension.
    In addition to them: `all()` and `any()` in Python also support short-circuiting,
    but this behavior is lost if comprehension is used. This affects performance.
    """
    my_fav_superheroes = ['sdf', 'sdfdf', 'dfgdfg']
    comma_seperated_names = ','.join(name for name in my_fav_superheroes)
    # === Use respective comprehension instead of unnecessary use of generators ===
    """It is unnecessary to use a generator expression within a call to list,
    dict or set since there are comprehensions for each of these types. Instead of
    using list/dict/set around a generator expression, they can be written as their
    respective comprehension."""
    squares = {i: i ** 2 for i in range(1, 10)}
    # === Using `else` where appropriate in a `for` loop ===
    """Python provides a built-in `else` clause for `for` loops. If a `for` loop
    completes
    without being prematurely interrupted by a `break` or `return` statement, then the
    `else` clause of the loop is executed."""
    numbers = [1, 2, 3]
    n = 4
    for num in numbers:
        if num == n:
            print("Number found")
            break
    else:
        print("Number not found")
    # === EAFP: “it’s easier to ask for forgiveness than permission”. ===
    """The Python community uses an EAFP (easier to ask for forgiveness than
    permission) coding style. This coding style assumes that needed variables, files,
    etc. exist. Any problems are caught as exceptions. This results in a generally
    clean and concise style containing a lot of try and except statements."""
    dict_ = {}
    value = None
    try:
        value += dict_["key"]
        print(value)
    except KeyError:
        pass
    # === Comparing with None/Boolean values in the Correct way ===
    number = None
    flag = True
    # ... many lines after:
    if number is None or flag is True:
        print("PEP 8 Style Guide prefers this pattern")
    # === Using wildcard imports * ===
    from collections import Counter
    # === Use zip() to iterate over a pair of lists ===
    numbers = [1, 2, 3]
    letters = ["A", "B", "C"]
    for numbers_value, letters_value in zip(numbers, letters):
        print(numbers_value, letters_value)
    # === Using set/dict to check if key is in list (especially very long) ===
    """Using key in list to iterate through a list can potentially take n iterations
    to complete, where n is the number of items in the list.
    If possible, you should change the list to a set or dictionary instead, because
    Python can search for items in a set or dictionary by attempting to directly
    accessing them without iterations, which is much more efficient (Since set in
    Python
    is implemented as a hash table and time complexity of accessing an element is O(1)).
    This is not applicable if your list has duplicates, and you want to find all
    occurrences of the element.
    """
    original_list = [1, 2, 3, 4]
    s = set(original_list)
    if 4 in s:
        print("The number 4 is in the list.")
    else:
        print("The number 4 is NOT in the list.")
    # === Use `get()` to return default values from a dictionary ===
    """Frequently you will see code create a variable, assign a default value to the
    variable, and then check a dictionary for a certain key. If the key exists,
    then the value of the key is copied into the value for the variable. While there
    is nothing wrong this, it is more concise to use the built-in method dict.get(
    key[, default]) from the Python Standard Library. If the key exists in the
    dictionary, then the value for that key is returned. If it does not exist,
    then the default value specified as the second argument to get() is returned."""
    stocks = {'AMZ': 'Amazon', 'AAPL': 'Apple'}
    stock_name = stocks.get('AMC', 'undefined')
    # === Use items() to iterate over a dictionary ===
    """The `items()` method on a dictionary returns an iterable with key-value tuples
    which can be unpacked in a `for` loop. This approach is idiomatic, and hence
    recommended.
    """
    country_map = {'us': "USA", 'uk': "United Kingdom"}
    for code, name in country_map.items():
        # do something with name
        pass
    # === Use explicit unpacking ===
    """Unpacking is more concise and less error-prone as manually assigning multiple
    variables to the elements of a list is more verbose and tedious to write."""
    cars = ['BMW', 'Mazda', 'Tesla']
    cars2 = ['BMW', 'Mazda', 'Tesla']
    cars3 = ('BMW', 'Mazda', 'Tesla', 'Audi')
    car_1, car_2, car_3 = cars
    car_1, car_2, _ = cars2  # _ -> if we don't need variable name for other values
    car_1, car_2, *car_3 = cars3  # car_1='BMW', car_2='Mazda', car_3=('Tesla', 'Audi')
    # === Use enumerate() in loops ===
    """Creating a loop that uses an incrementing index to access each element of a
    list within the loop construct is not the preferred style for accessing each
    element in a list. The preferred style is to use enumerate() to simultaneously
    retrieve the index and list element."""
    cars = ['BMW', 'Mazda', 'Tesla']
    for index, car in enumerate(cars):
        print(index, car)
    # === Prefer returning one object type in function call ===
    def returning_many_types_from_function_call_good(name=None, db=None):
        """
        Having inconsistent return types in a function makes the code confusing and
        complex to understand, and can lead to bugs which are hard to resolve. If a
        function is supposed to return a given type (e.g. integer constant, list,
        tuple) but can something else as well, the caller of that function will always
        need to check for the type of value being returned. It is recommended to return
        only one type of object from a function.
        If there's a need to return something empty in some failing case,
        it is recommended to raise an exception that can be cleanly caught.
        :return:
        """
        person = db.get_person(name)
        if not person:
            raise Exception(f'No person found with name {name}')
        return person.age  # guaranteed to return int every time
    # === Use literal syntax to initialize empty list/dict/tuple ===
    """
    It is relatively slower to initialize an empty dictionary by calling dict() than
    using the empty literal, because the name dict must be looked up in the global
    scope in case it has been rebound. Same goes for the other two types — list() and
    tuple().
    """
    my_evans = []
    my_maps = {}
    my_tuple = ()
    my_set = set()  # because there is no literal syntax for set
    # Add something to this empty list
    # === Audit code thoroughly! in production code ===
    """
    Most of us have done this at least once — while debugging code, it may happen
    that you push your code after you found the bug but forgotten to remove the
    debugger. This is critical, and can affect the behavior of the code. It is highly
    recommended to audit the code to remove invocations of debuggers before checking in.
    :return:
    """
    # remove all breakpoint()/print()/pdb
    # ...some ready for dev/qa/stage/prod envs business logic...