Make or break deal: Quick Python Refresher
Why use Python?
In a statically typed language, the source code is converted into machine code (machine-readable format) during compile time. This implies that the type checking of the variables happens at the compile time.
Examples of Statically Types Languages: C, C++, JAVA, Go
Python is a dynamically typed language, so the type checking of variables happens at the run time -> thus, it produces less optimized code; variable assignments are dynamic and can be altered.
Does Python use a compiler?
Python uses a compiler, but whether a JIT compiler is used or not depends on the type of Python implementation being used. Python reads the source code (.py file) into the memory. Parses it to get the bytecode (this is where a compiler is used). A bytecode is a lower-level, platform-independent representation of your code.?It contains instructions for performing specific operations like arithmetic operations by pushing or popping variables from a stack, compact numeric codes for representing an opcode and operands, making function calls and returning results to the caller, and controlling the flow of execution via conditions or loops. Python will load the bytecode (.pyc file in pycache folder located in the directory where the source code file resides- the Python compiled source file '.pyc' file is created if the Python process has write permission in your machine) directly into the Python Virtual Machine (a simulation of a computer system). Depending on the language implementation, the VM will either interpret the bytecode into a native code for execution purposes ( in the case of CPython implementation - actually, Cython does the compilation and CPython does the interpretation) or JIT compile it (in the case of PyPy implementation).?Python is classified as an interpreted language because it uses an interpreter for executing the bytecode. 'python.exe' and '/usr/bin/python3' are paths to the Python interpreter executable file on Windows and Unix-like systems like Linxs and MacOS, respectively.?The JIT compiler in PyPy can achieve a 10X or 100X speedup. However, this is an optimization technique rather than a fundamental shift in how a Python code is executed. Jython, IronPython, and Stackless are some other Python implementations.
It is to be noted that for a Python source file which is run as a script and is not imported as a module, the generated bytecode is not saved because it will be a one-time operation, so it avoids unnecessary file writes and cluttering the file system. Thus, saving the bytecode is an import optimization strategy. In Python, the compiler is always present at runtime and is part of the system that runs programs. This also helps in programming in an interactive prompt mode (RELP: Read, Evaluate, Print and Loop: Repeat from Read)
Examples of where Python is used are Google web search, YouTube, Netflix, Yelp, JPMorgan Chase & Co. for forecasting, and Cisco for hardware testing.
Python follows procedural programming, functional programming and object-oriented programming paradigms.
In procedural programming, the data is mutable (i.e. it can be altered). The flow of execution can be controlled using loops and conditionals.
In functional programming, the functions are pure functions because the data is immutable (i.e. it cannot change once it is created), so they do not produce any side-effect and produce the same output for the same input every time. The flow is controlled using recursion. The functions are first-class variables.?So, the functions can be
def call_func(f_name, a, b):
return f_name(a, b)
def perimeter_rectangle(a, b):
return 2 * (a + b)
def area_rectangle(a, b):
return a * b
print(call_func(area_rectangle, 2, 3)) # Output: 6
print(call_func(perimeter_rectangle, 2, 3)) # Output: 10
2. Assigned to a variable
def greet(name):
return f'Hello, {name}!'
var1 = greet
print(var1('Arup'))
3. Return a function defined inside another function (i.e. it can return a nested function). - This helps to dynamically call functions and create flexible function manipulation and general-purpose utilities. This is useful in higher-order functions like map, filter, reduce, and decorators. Higher-order functions take one or more functions as their arguments and return a function as its result.
def multiplier(multiply):
def calculate(number):
return number * multiply
return calculate #Notice we did not actually call the function calculate over here instead we have returned the function -> This property will allow us to decorate a function.
doubler = multiplier(2)
print(f"multiplier returns calculate which is used to make doubler: {doubler(10)}") # Output: multiplier returns calculate which is used to make doubler: 20
4. Stored in a data structure like a hashmap, list, etc.
def add(a, b):
return a + b
def multiply(a, b):
return a * b
operation_dict = { "add": add, "multiply": multiply}
print(operation_dict["add"](3,4), ",", operation_dict["multiply"](3,4)) # Output: 7 , 12
Everything is an object in Python, and so are functions. A function is an instance of the Object type. In Python, all the objects ultimately inherit from the 'object' class. Functions have attributes, too, like?doc,?which gives the documentation of the function,?and name,?which lists the name of the function.
def greet(name):
"""Greet a person"""
return f"Hello, {name}!"
print(greet.__doc__) #Output: Greet a person
print(greet.__name__) #Output: greet?
What are lambda functions in Python?
Lambda functions are anonymous functions, which are functions without names. They are simple, short-lived, throw-away functions.?Normal functions are defined using the def keyword, but anonymous functions are defined using the lambda keyword. Syntax:
lambda arguments: expression -> There can be multiple arguments, but only one expression
Example:
领英推荐
multiply = lambda a,b,c: a*b*c
print(multiply(1, 2, 3))
Understanding Pass-by-Reference and Pass-by-Value in Python: Mutable vs Immutable Types
In Python, when you pass a mutable object (like a list or a dictionary) to a function, it is passed by reference, not by value. This means that any modifications made to the object inside the function will reflect outside the function as well.
For immutable types (like integers, strings, and tuples), if you need to modify the value, you cannot do so in place. Instead, you need to return a new modified value and assign it back in the caller.
def incrementNumber(number, val):
# Since integers are immutable, we return the modified value
return number + val
# Example usage
num = 10
num = incrementNumber(num, 5) # num is updated by assigning the new value
print(num) # Output: 15
OOPs - object-oriented programming aims to implement real-world entities like inheritance, polymorphism, encapsulation, etc., in programming by binding data and their associated functionality into a single unit called class. A class is a blueprint or template for creating objects. An object is an instance of a class.
init() - is a class constructor in Python. Self is not a Python keyword but rather a convention used in the instance methods to refer to the instance calling the method. Without self, the method would not know which instance's attributes to use. The variables defined within the init() are called instance variables or objects.
Inheritance allows a class to derive or inherit properties from another class (base class or parent class). Inheritance promotes code reusability. The expression object.attribute searches the inheritance tree of linked objects in a bottom-up manner, trying to find the lowest occurrence of the attribute. In Python, every class inherits from a built-in basic class called 'object'.
Types of inheritance in Python:
super() is a built-in function that returns the objects representing the parent class. a private attribute is a convention used to indicate that an attribute is intended for internal use within a class and should not be accessed directly from outside the class. This is achieved by prefixing the attribute name with an underscore (_) or double underscore (__). A double underscore triggers name mangling, where the attribute name is changed to include the class name. Private attributes reduce the risk of name conflicts between the class's internal attributes and attributes that might be used in subclasses or external code.
class Example:
def init(self):
self.__private_attribute = "This is private"
def get_private_attribute(self):
return self.__private_attribute
# Accessing the private attribute
example = Example()
print(example.get_private_attribute()) # Correct way to access
print(example.__private_attribute) # Raises AttributeError
print(example._Example__private_attribute) # Directly accessing mangled name
Polymorphism means having many forms. Method overriding in the child class is an example of polymorphism.
A package is a directory containing a special file called init.py and potentially other Python files (modules) and subdirectories. The init.py file indicates that the directory should be treated as a package. This init.py file can be left blank or can be coded with the initialization code for the package.
Garbage collection in Python:? All dynamically created objects are allocated memory in the heap. Garbage collection is enabled by default in Python and runs periodically to clear heap memory for all unreachable objects. Garbage collection helps to automatically reclaim unused or inaccessible memory. It helps prevent memory leaks, optimize memory usage and ensure efficient program memory allocation. Python uses two strategies for memory allocation.
a.) Generation 0: This is the youngest generation, consisting of newly allocated objects. These objects are collected most frequently because most of these objects become unreachable quickly.
b.) Generation 1: Objects that survive garbage collections in Generation 0 or are used in a different program than where the memory was allocated are promoted to Generation 1. This generation is collected less frequently.
c.) Generation 2: Objects that survive multiple garbage collection cycles in Generation 1 are promoted to Generation 2. This generation is collected the least frequently.
?
The threshold value for every generation can be obtained by importing the gc module and executing gc.get_threshold(). The threshold value for triggering the garbage collector can be modified by using the gc.set_threshold() method.
Manual garbage collection can be done in two ways using gc.collect() method:
To disable garbage collection, use gc.disable().
For Python design patterns, refer: https://refactoring.guru/design-patterns/python
Also, an interesting read would be: "Ownership and Borrowing in Rust".