Make or break deal: Quick Python Refresher

Make or break deal: Quick Python Refresher

Why use Python?

  1. Software quality: Readability-> making it reusable and maintainable, object-oriented and functional programming.
  2. Developer productivity: Python requires fewer lines of code, which increases developer productivity.
  3. Portability: Python uses an interpreter instead of a compiler, which converts source code to platform-dependent machine code. Thus, whichever version supports the Python interpreter, the code can be run over there without any change. Also, the use of standard library, virtual environment and consistent syntax.

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

  1. Passed as an argument to a function - This shows that functions are objects which can be passed as parameters.

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:

  1. Single Inheritance: Derived class inherits properties from single parent class.
  2. Multilevel inheritance: Derived class inherits properties from its immediate parent class, which in turn inherits properties from its parent class.
  3. Multiple inheritance: Derived class inherits properties from more than one parent class. The left-to-right order of the classes mentioned in the parenthesis of the class header line specifies the order in which the superclasses will be searched for attributes by the inheritance tree.
  4. Hierarchical inheritance: More than one derived class inherits properties from a parent class.

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.

  1. Reference counting: When you allocate memory to a variable, you associate a counter with it to account for how many parts of the program still use it. When the count reduces to zero, the memory allocated to the variable is freed. A cyclical reference or reference cycle is said to occur when an object's reference counter can never reach o (due to a cycle). Thus, the reference counter cannot destroy the object.
  2. Garbage collection: Garbage collection can be used to destroy reference cycles. Python's Garbage collector uses a generational approach to manage memory.? It automatically runs when the number of allocations minus the number of deallocations exceeds a threshold for a given generation.

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:

  1. Time-based garbage collection occurs after a fixed time interval.
  2. Event-based garbage collection occurs on an event occurrence. For example, the user exits the application or enters an idle state.

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".

要查看或添加评论,请登录

社区洞察

其他会员也浏览了