Introduction
Python is one of the popular and general programming languages which is used in every domain.
Let's do a walkthrough on all the important aspects of this language, focusing more on the code snippets.
Compiler
Converting high-level language (Python code) to low-level language (Byte-code). One of the main advantages it does is syntax checks.
Variables
Python is a dynamic-typed language, so we can change the type of variable at a given point. For example:-
def main():
x = 20
print("First :", x)
x = "Twenty two"
print("Second :", x)
is_check = False
is_none = None
main()
"""
Output
First: 20
Second: Twenty two
"""
Some advanced data structures are as follows List | Set | Dict | Tuples.
- List, Sets and Dict are mutatable data-structure.
lst = [1, 2, 3, 5, 8]
unqiue_set = {2, 3, 2, 4}
new_dict = {"first": 1}
tu = (1, 2, 3)
Conditional statement
The main conditional keywords are if
, elif
and else
.
def main():
x = 10
y = 20
z = "check"
if x and y or not y and x:
print("First")
elif c in z:
print("Second")
else:
print("Third")
Looping
Here we are using range
function which would start from 0 to 4 in the below example.
def main():
count = 0
for i in range(0, 5):
count += 1
print(i)
while count < 10:
print(count)
count += 1
List Comprehension
lst = [i for i in range(1, 11) if i % 2 == 0]
Exceptions
def main():
try:
a = 10 / 0
except Exception as e:
print("Exception due to various logging: ", e)
finally:
print("Completion")
main()
Scope
x = 1
def main():
x = 0
print(x) # 0
main()
print(x) # 1
value = 0
def main():
# this is bad practice!!!
# asking compiler update value in the global level,
# basically this is best example of side-effect
global value
value = 5
print(value) # 0
main()
print(value) # 5
Sorting
lst = [2, 3,10, 5]
list.sort(reverse=True) # mutate the original list
sorted(list, reverse=True) # return sorted list
Modules and Packages
__name__
is a special variable that provides the current execution file like "__main__". The imported modules are not considered as main, only the one which is the entry point of execution.
# functions.py
def foo():
print("Foo")
# main.py
import functions as f
# OR `from functions import foo`
# OR `from functions import *` -> to access everything from the file, bad
# practice.
if __name__ == "__main__":
f.foo()
# code/__init__.py -> This file make it a package
print("Init")
# code/abc.py
print("ABC")
# code/def.py
print("DEF")
# main.py
import code.abc # this will print the Initi and then ABC once
if __name__ == "__main__":
print("main")
# code/__init__.py
import code.abc
import code.def
# code/abc.py
print("ABC")
# code/def.py
print("DEF")
# main.py
import code # prints both ABC and DEF once.
- If the file is in the same package we can use
from . import abc
.
File
file = open("file.txt", "r") # reading mode
print(file.read())
file.close() # close the file always to keep memory leak
# OR
# context-manager `with`, it automatically close it.
with open("file.txt", "r") as file:
print(file.read())
readLine = file.readline() # create list of string spliting buy new-line
print(readLine)
print(readLine[0].strip()) # strip removes white-space and \n
when using w
mode or known as write mode allows writing data in the file, One key thing to remember is when using this mode if the already exists then it would be overridden by the code.
a
append mode adds the file cursor at the very last line of the file, and doesn't override. r+
mode reads + write. Using file.seek(0) moves the cursor to the beginning of the file.
Argument and Keyword arguments
Python provides a special operator to unlock unlimited arguments passing to a function.
def tail(*args, **kwargs):
print(args, kwargs)
tail([1, 2, 3], {"k": 2, "b": 3})
Lambda Functions
Inline functions or Arrow functions (coming from JS world), are used mostly with map or filter methods.
func = lambda x, y, z=0: x + y + z
print(func(1, 2))
lst = [1, 2, 3, 4, 5]
new_lst = map(lambda x: x**2, lst)
print(list(new_list))
filter_lst = filter(lambda y: y % 2 == 0, list)
Function closures
The function can be passed as an argument in another function (First call function).
def foo(x):
return x ** 2
def boo(func, y):
result = func(y)
return result
print(boo(foo, 12))
Iterator
Everything that can be looped over is iterators, like list
.
x = [1, 2, 3]
x_iter = iter(x) # or x.__iter__() are the same things
print(next(x_iter)) # 1 or we can use like x_iter.__next__()
print(next(x_iter)) # 2
print(next(x_iter)) # 3
Creating custom iterators
class Numbers:
def __init__(self, num, num2, num3):
self.num = num
self.num2 = num2
self.num3 = num3
def __iter__(self):
return NumberIterator(self.num, self.num2, self.num3)
class NumberIterator:
def __init__(self, one, two, three):
self.one = one
self.two = two
self.three = three
self.current = 0
def __next__(self):
self.current += 1
if self.current == 1:
return self.one
elif self.current == 2:
return self.two
elif self.current == 3:
return self.three
else:
raise StopIteration
nums = Numbers(1,2, 3)
for val in nums:
print(value)
Generator
Same as the Iterator. Used in modern programming, iterators are more legacy ways of doing things.
def gen():
yield 1
yield 2
yield 3
itr = gen() # Generator object
next(itr) # 1
next(itr) # 2
next(itr) # 3
for i in itr:
print(i)
"""
Why this is optimal then regular fib, because in that case would have be required
to store it in the list which would keep track of all the data, while
in this way we are just concerned with last value.
"""
def fib(n):
last = 1
second_last = 1
current = 3
while current <= n:
num = last + second_last
yield num
second_last = last
last = num
current += 1
for i in fib(10):
print(i)
Threads and Processes
Process: This is just a program that is executed by the operating system.
Thread: It is what makes of process, it's part of the process, multiple threads make a process.
For example, if you have 6 cores then 6 processes or instructions can run at the same time in parallel.
There is a new tech called Hyper-threading, which is logical core and physical core. One physical core is broken into multiple logical cores which means every logical core is capable of executing instructions at the same time.
Global interpreter Lock
At a time only one thread would be using the interpreter, everything else would be locked. This is specific to Python, that's why you cannot have parallelism in Python. Lock <=> Mutex.
from threading import Thread, Lock
from time import sleep
def run(log_str, lock: Lock):
lock.acquire() # waiting mode
sleep(1)
print(log_str)
lock.release() # released mode
lock = Lock() # thread2 won't be executed until acquire is not released
thread1 = Thread(target=run, args=("run 1", lock))
thread2 = Thread(target=run, args=("run 2", lock))
# Execute the thread callback
thread1.start()
thread2.start()
Asynchronous Execution
From the later version of the python, we have the introduction of async in the language which provides a single-threaded concurrency approach to execution.
import asyncio
async def population():
await asyncio.sleep(1)
print("population")
async def main():
print("main") #1
await asyncio.sleep(2) #2 await is required in case of async
task = asyncio.create_task(population()) # will continue with next line of code
print("main end") #3
await task #4 wait until async is not completed
# this function is required in order to start the async entry point
asyncio.run(main())
Conclusion
As we conclude, may these insights foster a deeper appreciation for Python's capabilities, propelling developers into a world where their imaginations become the blueprint for innovation.