Python Notes
Tags: python, django
Published: 2023-07-09 14:33:57

**History**

1. Python was founded by Guido and it's name taken from a BBC Comedy Series **Monty Python's Flying Circus** because of it's complete and mysterious nature which python also has.
2. It's a dynamically types language and strongly typed language.
3. It's an interpretation language. 

**Syntax Rule**
1. It requires indentation. At least one space is required for a block.

```python
if 2>3:
 print("Hello World")
```
2. You can use Unicode symbol in python.

**Operators in Python**

1. Arithmetic Operator
2. Assignment Operator
3. Logical Operator
4. Bit wise Operator
5. Comparison operator
6. Identity Operator
7. Membership Operator

**pip and Virtual Environment**

1. pip is a python package manager which we can use to install packages from python package index.
2. You can create virtual environment where you can have complete copy of python, pip etc. It will help you to avoid dependencies error etc.
3. Install virtualenv globally
```bash
pip install virtualenv
```
4. Create virtual environment using
```python
virtualenv env
```
5. Activate environment on 
linux
```python
source /env/bin/activate
```
Windows
```python
env\Scripts\activate
```
6. pipenv makes it easy because it combines pip and virtual environment in one single tool. It generate pipfile which has all dependencies with their versions. It also generates pipfile.lock which lock the versions of the packages so that they work fine.
 **Control Structures**
1. While Loop:
The while statement repeats a body of code as long as a condition is true.
2. For Loop:
It's mostly use to iterate over number of items.
3. list comprehension:
A list comprehension allows you to easily create a list based on some processing or selection criteria.
```python
l1 = [x for x in range(0,5)]
print(l1)
[0,1,2,3,4]
```
**Type Casting**
1. Type Conversion is the conversion of an object from one data type to another data type.
2. Implicit Type Conversion is automatically performed by the Python interpreter.
3. Python avoids the loss of data in Implicit Type Conversion.
4. Explicit Type Conversion is also called Type Casting, the data types of objects are converted using predefined functions by the user.
5. In Type Casting, loss of data may occur as we enforce the object to a specific data type.

**Exception Handling**

1. try/except block
If the code in try block failed then the code in except block will run
2. try/except/else block
If the code in the try block failed then except block code will run and if the code in try block becomes successful then the code in the else block will run. If any exception occurs in the else block code then the above try/except block will not work. 
3. try/except/else/finally block
It has an extra finally block which always run either the code makes an error or not.

**Functions**
1. Benefits of function are **reuseability and readability**
**Partial Function**
Partial functions allow us to fix a certain number of arguments of a function and generate a new function.
e.g
```python
def f(a, b, c, x):
    return 1000*a + 100*b + 10*c + x
  
# A partial function that calls f with
# a as 3, b as 1 and c as 4.
g = partial(f, 3, 1, 4)
  
# Calling g()
print(g(5))

```
**Lambda Function**
In Python, a lambda function is a special type of function without the function name. For example,
```python
greet = lambda name: print("Hello", name)
greet("sami")
add = lambda a,b:print(a+b)
print(add(1,2))
```
We can use **filter** or **map** to find filter out our list. E.g
Filter return new list with new values and map return True or False list.
```python
list1 = [1,2,3,4,5]
list2 = list(filter(lambda x:x%2==0,list1))
print(list2)
# The Output will be -> [2,4]
```
**Monkey Patching**
In Python, the term monkey patch refers to dynamic (or run-time) modifications of a class or module. In Python, we can actually change the behavior of code at run-time.

**Collection (Arrays)**
There are four types of collection data types in python.
1. List
2. Set
3. Dictionary
4. Set

1.**List**
List is a collection which is ordered and changeable. Allows duplicate members.
Time Complexity: O(1)
Space Complexity: O(1)
List operations:
append,pop,remove,del sort, reverse, clear, and insert
We use the extend() method to add all the items of an iterable (list, tuple, string, dictionary, etc.) to the end of the list
```python
numbers = [1, 3, 5]

even_numbers = [4, 6, 8]

# add elements of even_numbers to the numbers list
numbers.extend(even_numbers)

print("List after append:", numbers) 
```
2.**Tuple**
Tuple is a collection which is ordered and unchangeable. Allows duplicate members. Tuples are immutable 
**Operations**
append,comp,len,min,max,tuple
3.**Sets**
A Set in Python programming is an unordered collection data type that is iterable, mutable and has no duplicate elements. This is based on a data structure known as a hash table. Since sets are unordered, we cannot access items using indexes as we do in lists.
**Frozen sets **in Python are immutable objects that only support methods and operators that produce a result without affecting the frozen set or sets to which they are applied. It can be done with frozenset() method in Python.

While elements of a set can be modified at any time, elements of the frozen set remain the same after creation. 
**Operations:**
add,discard, union, intersection, difference
4.**Dictionary**
Dictionary is a collection which is ordered (prior to Python 3.6, dictionaries were
unordered) and changeable. No duplicate members.
**Operations**
pop, popitem, del, insert new values

**Strings**
1. Escape Sequencing in Python
You use single quotes, double quotes or triple quotes for printing a string but you can't use quotes inside the string.So, we use backslash for it. The element next to backslash deal in new meanings rather than literal meanings.

```python
l1 = "\"Hello World\""
```
2. Formatting in Strings
We use _format()_ function for formatting 
Strings in Python can be formatted with the use of format() method which is a very versatile and powerful tool for formatting Strings. Format method in String contains curly braces {} as placeholders which can hold arguments according to position or keyword to specify the order.
```python
name = "Sami Ullah"
age = 20

print("My name is {} and My age is: {}".format(name,age))
```

**Methods**
Capitalize, lower, upper, endswith, startswith

**Object Oriented Programming**
1. Abstraction -> In this you hide implementation of an object from the user.
2. Encapsulation -> In this, you combine data and methods with in a class to protect the data from external interference. 
3. Inheritance -> It is a way to create new classes based on existing ones, allowing the new classes to inherit the attributes and methods of the parent class. 
4. Polymorphism -> It allows objects of different classes to be treated as if they were of the same class, making it possible to write generic code that can work with a variety of objects.

* There are two types of attributes 1) Instance Attribute -> This is described in constructor 2) Class Attribute -> This is described outside of the constructor.
**Name mangling** is a mechanism we use for accessing the class members from outside. The Python interpreter rewrites any identifier with “__var” as “_ClassName__var”. And using this you can access the class member from outside as well.
Namespaces and Scopes

* When Python tries to interpret your code, one key task is to understand what variables and functions that you refer to. To get the job done, Python uses namespaces to track the variables and functions that it can search. Specifically, you can think of namespaces as dictionaries that store all the variables that are available to you — they are tools in your tool boxes.

* Scopes are a concept closely related to namespaces. Scopes define boundaries where Python can resolve the variables (resolve means Python understands what variables it should use). As mentioned above, namespaces are tracking the tools in your boxes, while scopes are different tool boxes — some are larger and some are smaller (local scope for a function) and embedded in larger scopes (e.g., global scope for the module).

* In Python, an **abstract function** is a function that is declared but does not have an implementation. It serves as a blueprint for subclasses to override and provide their own implementation. Abstract functions are typically used in the context of abstract classes or interfaces to enforce certain behaviors or contracts.

```python

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

    def perimeter(self):
        return 2 * 3.14 * self.radius

```

**Constructors**
There are two types of constructors. 
1- Default Constructor
2- Parametrized Constructor
**Class Methods**

Let’s compare that to the second method, MyClass.classmethod. I marked this method with a @classmethod decorator to flag it as a 

**Static methods are like simple methods but belong to class**

**classmethod receives the class as the first parameter (cls) and can access class-level data, while staticmethod does not receive any special parameters and does not have access to instance or class-level data.**

**Inheritance Types**
There are 5 inheritance types.
1. Single Inheritance
2. Multiple Inheritance
3. Multilevel Inheritance
4. Hierarchical Inheritance
5. Hybrid Inheritance

**Static Variable and Static methods**
Static variables are related to class not objects and static methods related to class not objects. 
**Function Overloading vs Function Overriding **
Function overloading can be done without inheritance. In this we have two or more function with same name but with different parameters. Python doesn't support it. So, we can use parameterized functions to make it possible. It decide on compile time. It's make our code complex.
While Function overriding occurs in inheritance. It need two classed at least. The functions should have same name and it decide on run time. It's makes our code complex.

**File Handling**

```python
f = open(filename,mode)
```
**modes**
r -> read
w -> write and overridden data
a -> append the file 
r+ -> read and write data and overridden data
w+ -> write and read data and overridden data
a+ -> append and read data from the file. It won't be override existing data
You can remove file using **os module**
```python
os.remove('test.txt')
```

**Iterators**
An iterator is an object that can be iterated upon, meaning that you can traverse through all the values.

Iterators are methods that iterate collections like lists, tuples, etc. Using an iterator method, we can loop through an object and return its elements.

Technically, a Python iterator object must implement two special methods, __iter__() and __next__(), collectively called the iterator protocol.
```python
# define a list
my_list = [4, 7, 0]

# create an iterator from the list
iterator = iter(my_list)

# get the first element of the iterator
print(next(iterator))  # prints 4

# get the second element of the iterator
print(next(iterator))  # prints 7

# get the third element of the iterator
print(next(iterator))  # prints 0
```

**Generators**
A generator is a function that returns an object (iterator) which we can iterate over (one value at
a time).
Create Python Generator

In Python, similar to defining a normal function, we can define a generator function using the def keyword, but instead of the return statement we use the yield statement.
```python
def generator_name(arg):
    # statements
    yield something
```
Here, the yield keyword is used to produce a value from the generator.

When the generator function is called, it does not execute the function body immediately. Instead, it returns a generator object that can be iterated over to produce the values.
The yield keyword is used to produce a value from the generator and pause the generator function's execution until the next value is requested.

The for loop iterates over the generator object produced by my_generator(), and the print statement prints each value produced by the generator.

**Decorators**
A decorator is a design pattern in Python that allows a user to add new functionality to an
existing object without modifying its structure. Decorators are usually called before the definition
of a function you want to decorate.
```python
def maketea(func):
    func()
    print("Tea is making")
    

@maketea
def pre():
    print("Preperation has been done")


```
Here pre is sending as argument to maketea function

* In Python, a **first-class object** refers to a programming language feature where functions can be treated as values. This means that functions can be assigned to variables, passed as arguments to other functions, and returned as values from other functions. In Python, functions are first-class objects, which means they can be manipulated and used just like any other data type.

* **Chaining decorators** in Python can be done by applying multiple decorators to a function in a sequential manner
```python

@decorator1
@decorator2
def example(): print("Hello World")

```
**Shallow Copy vs Deep Copy**
A shallow copy constructs a new compound object and then (to the extent possible) inserts
references into it to the objects found in the original. A deep copy constructs a new compound
object and then, recursively, inserts copies into it of the objects found in the original.
Syntax of Deep copy

    Syntax: copy.deepcopy(x)

Syntax of Shallow copy

    Syntax: copy.copy(x)


**Random Numbers**
The Python standard library provides a module called random that offers a suite of functions for generating random numbers.

Python uses a popular and robust pseudorandom number generator called the **Mersenne Twister**.

We use seed() function if we want reproduce able random numbers. Otherwise random function use epoch(1971). 

**PEP 8**
Coding Style to follow
1. Consistent Indentation
2. The closing brace/bracket/parenthesis on multiline constructs may either line up under the first non-whitespace character of the last line of list or it may be lined up under the first character of the line that starts the multiline construct.
* Spaces are the preferred indentation method.
* Tabs should be used solely to remain consistent with code that is already indented with tabs.
* Python disallows mixing tabs and spaces for indentation.
* Import should be on seperate line
* Comments use and docstring use
**Linter**
Linting highlights **syntactical and stylistic** problems in your Python source code, which often helps you identify and correct subtle programming errors or unconventional coding practices that can lead to errors.
**Formatter**
Formatter only format your code. It doesn't analyze your code and find errors in it

**Difference between Linter and Formatter **
Linting is distinct from Formatting because linting analyzes how the code runs and detects errors whereas formatting only restructures how code appears.