Lambda, Comphrensions, Decorators and Generators with some sweet python tips and tricks.
python
Author
Aman Pandey
Published
November 4, 2020
Lambda and Comphresions
Functions in python are first class Objects that means you can assign them to variable, store them in data structure, pass them as a parameter to other functions and even return them from other function
Code
# addition function def add(x, y):return x+yprint (f" function is add(5,6) = {add(5,6)}")# you can assign them to other variablemyAdd = add# wait you can also delete the add function and the myAdd still points to underlying functiondel addprint (f" function is myAdd(5,6) = {myAdd(5,6)}")#functions have their own set of attributes print(f"{myAdd.__name__}")# to see a complete list of attributes of a function type dir(myAdd) in console
function is add(5,6) = 11
function is myAdd(5,6) = 11
add
Code
# functions as data structuresList_Funcs = [str.upper , str.lower , str.title]for f in List_Funcs:print (f , f("aI6-saturdays"))
<method 'upper' of 'str' objects> AI6-SATURDAYS
<method 'lower' of 'str' objects> ai6-saturdays
<method 'title' of 'str' objects> Ai6-Saturdays
So lambdas are a sweet Little anonymous Single-Expression functions
Code
add_lambda =lambda x , y : x+y # lambda automaticaly returns the value after colonprint(f"lambda value add_lambda(2,3)= {add_lambda(2,3)}") #you call lambda function as normal functions
lambda value add_lambda(2,3)= 5
You :- But Wait it’s an anonymous function and how can you give it a name
StackOverflow :- Relax, Searching for another example
Code
def someFunc(func): quote = func("We will democratize AI ")return quote# here the lambda function is passes to a normal function # the lambda here is anonymous and the parameter my_sentence = We will democratize AI # so we are adding some text of ours and returning the stringsomeFunc(lambda my_sentence: my_sentence+"by teaching everyone AI")
'We will democratize AI by teaching everyone AI'
Code
# here is one more exampletuples = [(1, 'd'), (2, 'b'), (4, 'a'), (3, 'c')]sorted(tuples, key=lambda x: x[1])
Code
# list comphrensionsl = [ x for x inrange(20)]even_list = [x for x in l if x%2==0]even_list_with_Zero = [x if x%2==0else0for x in l ]l , even_list ,even_list_with_Zero
Python’s decorators allow you to extend and modify the behavior of a callable (functions, methods, and classes) without permanently modifying the callable itself.
Any sufficiently generic functionality you can tack on to an existing class or function’s behavior makes a great use case for decoration. This includes the following:
logging
enforcing access control and authentication
instrumentation and timing functions
rate-limiting
caching, and more
Imagine that you some 50 functions in your code. Now that all functions are working you being a great programmer thought of optimizing each function by checking the amount of time it takes and also you need to log the input/output of few functions. what are you gonna do ?
Without decorators you might be spending the next three days modifying each of those 50 functions and clutter them up with your manual logging calls. Fun times, right?
Code
def my_decorator(func):return func # It's simple right it takes a function as it's parameter and returns itdef someFunc():return"Deep learning is fun"someFunc = my_decorator(someFunc) # it is similar to i = i + 1print(f" someFunc value = {someFunc()}")
someFunc value = Deep learning is fun
Code
# now just to add syntatic sugar to the code so that we can brag how easy and terse python code is # we gonna write this waydef my_decorator(func):return func @my_decorator# the awesomeness of this block of code lies here which can be used as toggle switchdef someFunc():return"Deep learning is fun"print(f" someFunc value = {someFunc()}")
someFunc value = Deep learning is fun
Stackoverflow :- Now that you got a little taste of Decorators let’s write another decorator that actually does something and modifies the behavior of the decorated function.
Code
# This blocks contains and actual implementation of decoratorimport time#import functoolsdef myTimeItDeco(func):#@functools.wraps(func)def wrapper(*args,**kwargs): starttime = time.time() call_of_func = func(*args,**kwargs) # this works because you can function can be nested and they remember the state function_modification = call_of_func.upper() endtime = time.time()returnf" Executed output is {function_modification} and time is {endtime-starttime} "return wrapper@myTimeItDecodef myFunc(arg1,arg2,arg3): # some arguments of no use to show how to pass them in code"""Documentation of a obfuscate function""" time.sleep(2) # just to show some complex calculationreturn"You had me at Hello world"myFunc(1,2,3) , myFunc.__doc__ , myFunc.__name__
(' Executed output is YOU HAD ME AT HELLO WORLD and time is 2.0032527446746826 ',
None,
'wrapper')
You :- Why didn’t I got the doc and the name of my function. Hmmm….
StackOverflow :- Great Programmers use me as there debugging tool, so use It.
Hints functools.wrap
StackOverflow : - Applying Multiple Decorators to a Function (This is really fascinating as it’s gonna confuse you)
Code
def strong(func):def wrapper():return'<strong>'+ func() +'</strong>'return wrapperdef emphasis(func):def wrapper():return'<em>'+ func() +'</em>'return wrapper@strong@emphasisdef greet():return'Hello!'greet()# this is your assignment to understand it hints strong(emphasis(greet))()
'<strong><em>Hello!</em></strong>'
Code
#Disclaimer Execute this at your own risk #Only 80's kids will remember thisimport disdis.dis(greet)
Context Managers
Code
# let's open a file and write some thing into itfile=open('hello.txt', 'w')try:file.write('Some thing')finally:file.close()
You :- ok now that I have wrote something into the file I want to read it, but try and finally again, it suck’s. There should be some other way around
StackOverflow :- Context manger for your Rescue
Code
withopen("hello.txt") asfile:print(file.read())
Some thing
You :- That’s pretty easy but what is with
Stackoverflow :- It helps python programmers like you to simplify some common resource management patterns by abstracting their functionality and allowing them to be factored out and reused.
So in this case you don’t have to open and close file all done for you automatically.
Code
# A good pattern for with use case is this some_lock = threading.Lock()# Harmful:some_lock.acquire()try:# Do something complicated because you are coder and you love to do so ...finally: some_lock.release()# Better :with some_lock:# Do something awesome Because you are a Data Scientist...
You :- But I want to use with for my own use case how do I do it?
StackOverflow :- Use Data Models and relax
Code
# Python is language full of hooks and protocol# Here MyContextManger abides context manager protocolclass MyContextManger:def__init__(self, name):self.name=name# with statement automatically calls __enter__ and __exit__ methods def__enter__(self): ## Acquire the lock do the processing in this methodself.f =open(self.name,"r")returnself.fdef__exit__(self,exc_type,exc_val,exc_tb): ## release the lock and free allocated resources in this methodifself.f:self.f.close()with MyContextManger("hello.txt") as f:print(f.read())
Some thing
You :- It works but what are those parameters in exit method
Stackoverflow :- Google It !
You :- But writing a class in python is hectic, I want to do functional Programming
StackOverflow :- Use Decorators
Code
from contextlib import contextmanager@contextmanagerdef mySimpleContextManager(name):try: f =open(name, 'r')yield ffinally: f.close()with mySimpleContextManager("hello.txt") as f:print(f.read())
Some thing
You :- Ok, that’s what I call a pythonic code but what is yeild
Stackoverflow :- Hang On!
Iterators and Generators
An iterator is an object representing a stream of data; this object returns the data one element at a time. A Python iterator must support a method called next() that takes no arguments and always returns the next element of the stream. If there are no more elements in the stream, next() must raise the StopIteration exception. Iterators don’t have to be finite, though; it’s perfectly reasonable to write an iterator that produces an infinite stream of data.
The built-in iter() function takes an arbitrary object and tries to return an iterator that will return the object’s contents or elements, raising TypeError if the object doesn’t support iteration. Several of Python’s built-in data types support iteration, the most common being lists and dictionaries. An object is called iterable if you can get an iterator for it.
Code
l = [1,2,3]it = l.__iter__() ## same as iter(l)it.__next__() ## gives 1next(it) ## gives 2next(it) ## gives 3next(it) ## gives error StopIteration
#lets replicate the simple range methodclass MyRange:def__init__(self,start,stop):self.start = start -1self.stop = stopdef__iter__(self):returnselfdef__next__(self):self.start =self.start+1ifself.start<self.stop:returnself.startelse:raiseStopIteration()for i in MyRange(2,10):print(i)
2
3
4
5
6
7
8
9
You :- Again a class
StackOverflow :- OK here’s a easy way Use Generators
They Simplify writing Iterators, kind of iterable you can only iterate over once. Generators do not store the values in memory, they generate the values on the fly so no storage is required. So you ask one value it will generate and spit it out
Code
def myRange(start,stop):whileTrue:if start<stop:yield start start = start+1else:returnfor i in myRange(2,10):print(i)
2
3
4
5
6
7
8
9
You’re doubtless familiar with how regular function calls work in Python or C. When you call a function, it gets a private namespace where its local variables are created. When the function reaches a return statement, the local variables are destroyed and the value is returned to the caller. A later call to the same function creates a new private namespace and a fresh set of local variables. But, what if the local variables weren’t thrown away on exiting a function? What if you could later resume the function where it left off? This is what generators provide; they can be thought of as resumable functions.
Any function containing a yield keyword is a generator function; this is detected by Python’s bytecode compiler which compiles the function specially as a result.
When you call a generator function, it doesn’t return a single value; instead it returns a generator object that supports the iterator protocol. On executing the yield expression, the generator outputs the value start , similar to a return statement. The big difference between yield and a return statement is that on reaching a yield the generator’s state of execution is suspended and local variables are preserved. On the next call to the generator’s next() method, the function will resume executing.
Code
# generator comphresnsivel = ( x for x inrange(20))l
filter(predicate, iter) returns an iterator over all the sequence elements that meet a certain condition, and is similarly duplicated by list comprehensions. A predicate is a function that returns the truth value of some condition; for use with filter(), the predicate must take a single value.
Code
# why did it returned an empty list think?[*map(lambda x :x **2 , l)]
[]
Code
[*filter(lambda x :x%2!=0,l)]
<filter at 0x7f6f9057f9e8>
zip(iterA, iterB, …) takes one element from each iterable and returns them in a tuple:
Code
z =zip(['a', 'b', 'c'], (1, 2, 3))for x , y in z:print (x,y)
a 1
b 2
c 3
Functools and Itertools (This is going to blow your mind)
These two python modules are super helpful in writing Efficient Functional Code
Code
# reducefrom functools importreducel = (x for x inrange(1,10))reduce(lambda x,y : x+y , l)
45
For programs written in a functional style, you’ll sometimes want to construct variants of existing functions that have some of the parameters filled in. Consider a Python function f(a, b, c); you may wish to create a new function g(b, c) that’s equivalent to f(1, b, c); you’re filling in a value for one of f()’s parameters. This is called “partial function application”.
The constructor for partial() takes the arguments (function, arg1, arg2, …, kwarg1=value1, kwarg2=value2). The resulting object is callable, so you can just call it to invoke function with the filled-in arguments.
Code
from functools import partialdef log(message, subsystem):"""Write the contents of 'message' to the specified subsystem."""print('%s: %s'% (subsystem, message)) ...server_log = partial(log, subsystem='server')server_log('Unable to open socket')
server: Unable to open socket
Code
from itertools import islice ,takewhile,dropwhile# here is a very simple implementation of the fibonacci sequence def fib(x=0 , y=1):whileTrue:yield x x , y = y , x+y
Code
list(islice(fib(),10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Code
list(takewhile(lambda x : x <5 , islice(fib(),10)))
[0, 1, 1, 2, 3]
Code
list(dropwhile(lambda x : x <5 , islice(fib(),10)))
[5, 8, 13, 21, 34]
Code
list(dropwhile(lambda x :x<11 , takewhile(lambda x : x <211 , islice(fib(),15))))
[13, 21, 34, 55, 89, 144]
To read more about itertools https://docs.python.org/3.6/howto/functional.html#creating-new-iterators
#Pythonic way calculatorDict = {"add":lambda x,y:x+y,"sub":lambda x,y:x-y,"mul":lambda x,y:x*y,"div":lambda x,y:x/y}calculatorDict.get("add",lambda x , y:None)(2,3)
5
Code
# because we are repeating x,y in all lambda so better approachdef calculatorCorrected(operator,x,y):return {"add":lambda :x+y,"sub":lambda :x-y,"mul":lambda :x*y,"div":lambda :x/y}.get(operator , lambda :"None")()calculatorCorrected("add",2,3)
5
Code
# How to merge multiple dictionariesx = {1:2,3:4} y = {3:5,6:7}{**x,**y}
{1: 2, 3: 5, 6: 7}
Code
# how to merge multiple list you gussed it correct a = [1,2,3]b=[2,3,4,5][*a,*b]
[1, 2, 3, 2, 3, 4, 5]
Code
# Named tuplefrom collections import namedtuplefrom sys import getsizeofvector = namedtuple("Vector" , ["x","y","z","k"])(11,12,212,343)vector,vector[0], vector.y # can be accessed lke list and dic
(Vector(x=11, y=12, z=212, k=343), 11, 12)
Code
# how to manage a dictionary with countfrom collections import Counterpubg_level3_bag = Counter()kill = {"kar98":1 , "7.76mm":60}pubg_level3_bag.update(kill)print(pubg_level3_bag)more_kill = {"7.76mm":30 , "scarl":1 , "5.56mm":30}pubg_level3_bag.update(more_kill)print(pubg_level3_bag)