# πŸ‘€ Advanced Python Concepts

advancedpythonconceptsgeneratoriteratorclosuredecorators

# πŸš’ Generators

Generators are used to create iterators, but with a different approach. Generators are simple functions which return an iterable set of items, one at a time, in a special way.

yield

Generators are always accompanied by yield

import random

def lottery():
    # returns 6 numbers between 1 and 40
    for i in range(6):
        yield random.randint(1, 40)

    # returns a 7th number between 1 and 15
    yield random.randint(1,15)

for random_number in lottery():
       print("And the next number is... %d!" %(random_number))

# πŸš‘ Generator Expression

Some simple generators can be coded succinctly as expressions using a syntax similar to list comprehensions but with parentheses instead of square brackets.

>>> sum(i*i for i in range(10))   # sum of squares
285

# πŸ‘¨β€πŸ‘§β€πŸ‘§ Iterators

  • for calls iter(), __iter__() which called next() and subsequently __next__()
  • Writing your own iterators?
class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

# πŸ’  Closure

A closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

Nested Functions

It's very important to note that the nested functions can access the variables of the enclosing scope. However, at least in python, they are only readonly.

How is it ddifferent from javascript closure?

def outer_func():
    message = 'Hi'

    def inner_func():
        print(message)

    return inner_func

Why use closures?

Closures don't give you any extra power.

Anything you can achieve with them you can achieve without them.

But they are very usable for making code more clear and readable. And as we all know clean readable short code is a code that is easier to debug and contains fewer bugs.

Something they could be more optimized and more redable

# πŸ‘‘ Decorators

Preprocessing/Postprocessing the arguments/returns to decorated function

  • Can add multiple decorators to a function
  • Decorators in Python are used to modify or inject code in functions or classes. Using decorators, you can wrap a class or function method call so that a piece of code can be executed before or after the execution of the original code. Decorators can be used to check for permissions, modify or track the arguments passed to a method, logging the calls to a specific method, etc.

# 🎠 Coroutines

Coroutines can be entered, exited, and resumed at many different points. They can be implemented with the async def statement.

# πŸ“ Memory Management

Python does automatic memory management (reference counting for most objects and garbage collection to eliminate cycles). The memory is freed shortly after the last reference to it has been eliminated.

Python memory is managed by Python private heap space. All Python objects and data structures are located in a private heap. The programmer does not have an access to this private heap and interpreter takes care of this Python private heap.

# πŸ”’ GIL - Global Interpreted Lock

Only specific to CPython implementation

# πŸ’» Metaprogramming

Metaprogramming treat other programs as data

# 🐡 Monkey Patching

What is monkey patching? How to use it in Python? Example? Is it ever a good idea?

Default car doesnt have a harness to carry bicycles. We monkey patch it to a car.

# βš”οΈ Descriptors

used to describe something

In general, a descriptor is an object attribute with β€œbinding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.

# πŸ˜„ Good Questions

  • Is it possible to have a producer thread reading from the network and a consumer thread writing to a file, really work in parallel? What about GIL?
  • Dependency
    • Functional Dependencies FDs
      • If
      • duplicated are acceptable as long as the condition is satisfied
    • Direct Dependency
    • Transitive Dependency
      • Third Normal Form 3NF - Avoid transitive dependency to save space
    • Partial Dependency 2NF
    • 1NF remove all Multivalued (multiple phone nos) and Composite (Residence Address) date entries
      • by breaking/decomposing data into atomic values seperate tables
  • What are the wheels and eggs? What is the difference?
  • When to change version? Major | Minor version change
    • When not backward compatible => Major change
      • Example python 2 and python 3

# πŸ”’ Types

Types in > python 3.5

def infinite_stream(start: int) -> Generator[int, None, None]:
    while True:
        yield start
        start += 1

# πŸ”± Python Magic Variables

What does __all__ magic variable do in python?

It's a list of public objects of that module, as interpreted by import *.

It overrides the default of hiding everything that begins with an underscore.

For example, the following code

in a foo.py explicitly exports the symbols bar and baz

# foo.py
__all__ = ['bar', 'baz']

waz = 5
bar = 10
def baz(): return 'baz'

These symbols can then be imported like so:

from foo import *
print(bar)
print(baz)
# The following will trigger an exception, as "waz" is not exported by the module
print(waz)
What is the difference between __getattr__ and __getattribute__?

means get object attribute

With objects, you need to deal with its attributes. Ordinarily we do instance.attribute. Sometimes we need more control (when we do not know the name of the attribute in advance).

For example, instance.attribute would become getattr(instance, attribute_name). Using this model, we can get the attribute by supplying the attribute_name as a string.

# 🀳 References