# π Advanced Python Concepts
# π 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
callsiter()
,__iter__()
which callednext()
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
- Metaclasses are provided by python
- a metaclass is a class whose instances are classes.
- instance of a normal class is an bounded object in memory
- https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
# π΅ 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
- If
- 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
- Functional Dependencies FDs
- 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
- When not backward compatible => Major change
# π’ Types
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 codein a
foo.py
explicitly exports the symbolsbar
andbaz
# 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
β New Python Features Algorithms β