Thursday, 14 December 2023

Python Fundamentals



For Python Tutorials Follow this link

https://docs.python.org/3/faq/index.html

Learn Python in 10 min
>>> help(5)
Help on int object:
(etc etc)

>>> dir(5)
['__abs__', '__add__', ...]

>>> abs.__doc__
'abs(number) -> number
# Creating Virtaul Environment 

For creating Virtual environment Use the Below command

python -m venv env -- For creating new virtual environement

For Activating the Virtaul Environment 
.\env\Scripts\activate -- For activating virtual environment.

or
.\activate

For installing the Modules 

Then install using PIP

For Deactivating the Virtual Environment 

deactivate -- For deactiving virtual environment.

##########################################################################################

#The Usage of *args and **kwargs
*args --- It accepts any no of positional arguments like list,String,integers and Varchar
**kwargs --- It accepts only Dictionary Arguments .
Let's see some basic examples on this 

>>> def example_function(*args):
...     for arg in args:
...         print(arg)
...
>>> example_function(1, 2, "hello", [3, 4])
1
2
hello
[3, 4]

and

>>> def example_function(**kwargs):
...     for key, value in kwargs.items():
...         print(key, ":", value)
...
>>> example_function(name="Alice", age=25, city="Wonderland")
name : Alice
age : 25
city : Wonderland
##########################################################################################

# Python Code formatter 
Install the Below IDE to write the Python code which will correct the errors in VSCode 
  • Pylint
PIP install Pylint
and then write the code in VsCode
##########################################################################################

# Using Python connecting to the sqlite database.
import sqlite3
try:
   
    # Connect to DB and create a cursor
    sqliteConnection = sqlite3.connect('sql.db')
    cursor = sqliteConnection.cursor()
    print('DB Init')
 
    # Write a query and execute it with cursor
    query = 'select sqlite_version();'
    cursor.execute(query)
 
    # Fetch and output result
    result = cursor.fetchall()
    print('SQLite Version is {}'.format(result))
 
    # Close the cursor
    cursor.close()
 
# Handle errors
except sqlite3.Error as error:
    print('Error occurred - ', error)
 
# Close DB Connection irrespective of success
# or failure
finally:
   
    if sqliteConnection:
        sqliteConnection.close()
        print('SQLite Connection closed')
                               
##########################################################################################

# Python enumerate Function 

The enumerate function is used to iterate over a sequence (such as a list, tuple, or string) while keeping track of the index (position) of the current item. It returns pairs of the form (index, element). This can be particularly useful in loops where you need both the index and the corresponding value.

example-

seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]


The enumerate() function takes a collection (e.g. a tuple) and returns it as an key, value pair

##########################################################################################

# Using format Option in Print statement

>>> name = "Jyothi Nagendra"
>>> age = 34
>>> city = "Bhimavaram"
>>>
>>> # Using format option in print statement
>>> print("Name: {}, Age: {}, City: {}".format(name, age, city))
Name: Jyothi Nagendra, Age: 34, City: Bhimavaram

In this example, the curly braces {} act as replacers, and the format method is used to replace them with the values of the variables name, age, and city.

##########################################################################################
#Using f-strings

you can also use f-strings (formatted string literals) in the print statement

>>> name = "John"
>>> age = 25
>>> city = "New York"
>>>
>>> # Using f-string in print statement
>>> print(f"Name: {name}, Age: {age}, City: {city}")
Name: John, Age: 25, City: New York

here directly we can replace the flower braces with the values without format option by using f strings

##########################################################################################

# Using  floating-point number

# Example floating-point number
>>> pi = 3.141592653589793
>>>
>>> # Using f-formatting to print with 2 decimal places
>>> print(f"Value of pi: {pi:.2f}")
Value of pi: 3.14

In this example, :.2f inside the curly braces indicates that the floating-point number pi should be formatted with two decimal places. 

##########################################################################################

# Dunder Methods --

In Python, "dunder" is short for "double underscore," and "dunder methods" refer to special methods with names that start and end with double underscores.
They are used to define behavior for built-in operations in Python classes. For example, the __init__ method is a dunder method that is automatically called when an object is created.
By Default these dunder methods will be there if we create any class. These methods by default it will be there in object class since this object class acts as a parent for every class .

# Example to show dunder methods in a Class
Class A:
      pass

print(dir(A))
print(dir(obj))

Class A:
      pass

obj=A()
str(obj) 

When srring,arthemetic operation etc if we use by default it will look for the dunder method (__str__) in Class A if it is  not there it will check for parent object class and fetch it.
When we call object from methods like str,arthemetic operators ,class,len,in,abs,+,and,ceil,complex,contains,del,==,float,floor(math module),>=, then dunder methods (__) will be called  parent class of class is object class . 

Reference check this link from youtube.
https://www.youtube.com/watch?v=Dmp80lD_28U&list=PLwJGL4B6ur8zED5BCn8ms2GJrIPz8QEj9&index=122&pp=iAQB

##########################################################################################

# Python Generator Function 

In Python, a generator function is a special type of function that allows you to iterate over a potentially large sequence of data without creating the entire sequence in memory at once. It produces values one at a time using the yield keyword. When a generator function is called, it returns a generator object, which can be iterated over to retrieve the values produced by the yield statements.

>>> def myGenerator():
...     print('First Item')
...     yield 10
...     print('Second Item')
...     yield 20
...     print('Third Item')
...     yield 30
...
>>> gen = myGenerator()
>>>
>>> # Call next to advance the generator to the next yield statement
>>> next(gen)
First Item
10
>>>
>>> next(gen)
Second Item
20
>>> next(gen)
Third Item
30
##########################################################################################

Python Nested Loops Syntax:
-------------------------------------
Outer_loop Expression:
    Inner_loop Expression:
            Statement inside inner_loop
    Statement inside Outer_loop
##################################################################################
What is a List comprehension ?
List comprehension is a syntax construction to ease the creation of a list based on the existing iterable.

What is / and // in python 
// represents the floor division where as / represents precise division 
5//2 - 2
5/2- 2.5
###################################################################################
Instance Method Vs Class Method Vs Static Method 

Instance methods are used for operations that involve the instance of the class. Operate on instance data.Call On object instance

Class methods are used for operations that involve the class as a whole. Operate on class data or modify class behavior. Call on On class itself.

Static methods are used when a method doesn't need access to the instance or the class and can be thought of as standalone functions within the class. Function-like actions associated with a class but don't need access to class or instance data. Call On class or instance (no special first argument)

When I say "operate on instance data," I mean that instance methods can directly access and modify the data that's unique to each object (instance) of a class.

Here's a more detailed explanation:

  • Instance Data: Each object created from a class has its own set of attributes, called instance attributes. These attributes store the object's specific data, making each object unique.
  • Instance Methods: These methods are designed to work with this instance-specific data. They have access to the object's attributes through the self argument, allowing them to read, modify, and use those attributes.
class Person:
    def __init__(self, name, age):
        self.name = name  # Instance attribute
        self.age = age    # Instance attribute

    def greet(self):  # Instance method
        print("Hello, my name is", self.name)  # Accessing instance attribute

    def celebrate_birthday(self):  # Instance method
        self.age += 1  # Modifying instance attribute

person1 = Person("Alice", 30)
person1.greet()  # Output: Hello, my name is Alice (accessing instance data)
person1.celebrate_birthday()  # Modifying instance data
print(person1.age)  # Output: 31 (reflecting the change)

Key points:

  • Instance methods are called on individual objects, not the class itself.
  • They receive the object instance as self in their first argument.
  • They can access and modify any instance attributes of that object.
  • Each object's instance attributes are independent of other objects' attributes.

---------------------------------------------------------------------------------------------------------------------------
Class Methods-
-------------
When I say "class data," I'm referring to data that's shared by all instances of a class, rather than being specific to individual objects.

Here's a more detailed explanation:
Class Attributes: These attributes are defined directly within the class body, outside of any methods. They are shared by all instances of the class, meaning they have the same value for every object created from that class.
Accessing Class Data: Class methods and static methods can directly access and modify class attributes using the cls argument (for class methods) or by directly referencing the class name.

class Person:
    species = "Homo sapiens"  # Class attribute
    total_count = 0  # Class attribute (used for counting instances)

    def __init__(self, name, age):
        self.name = name  # Instance attribute
        self.age = age    # Instance attribute
        Person.total_count += 1  # Modifying class attribute

    @classmethod  # Class method
    def get_species(cls):
        return cls.species  # Accessing class attribute

    @classmethod  # Class method
    def get_total_count(cls):
        return cls.total_count  # Accessing and modifying class attribute

person1 = Person("Alice", 30)
person2 = Person("Bob", 25)

print(Person.get_species())  # Output: Homo sapiens (accessing class data)
print(Person.get_total_count())  # Output: 2 (reflecting the count of instances)

----------------------------------------------------------------------------------------------------------------------------
Static Method Example 

class Person:
    @staticmethod
    def is_adult(age):
        return age >= 18

Person.is_adult(25)  # Output: True

###################################################################################

can we modify class data by using getter setter methods in python ?

Yes, you can modify class data using setter methods in Python, but not directly with getter methods. Here's how they work:

class Person: @property def name(self): return self._name @name.setter def name(self, new_name): self._name = new_name

Modifying data using a setter methods outside the class: person = Person() person.name = "Alice" # This calls the setter method to set the name

Getter methods: Primarily provide a way to retrieve the value of an attribute Defined using the @property decorator.GETTER methods are (READ ONLY PROPERTY) while the SETTER methods are READ WRITE property.
While they cannot directly modify data, they can indirectly trigger changes through other methods or logic within their code.

Key points: Setter methods are essential for controlled modification of class data, ensuring data integrity and consistency. Getter methods, while not directly modifying data, play a crucial role in encapsulation and controlled access to class attributes. Together, getters and setters promote a well-structured and maintainable object-oriented design.

##########################################################################
Escape Characters

>>> print('Hi this is Nagendra\'s blog') Hi this is Nagendra's blog

Here to escape ' in Nagendra's I am using \ as a character .
\n -- New Line Charecter
\b -- BackSpace
\t -- Tab
\ -- Escape Symbol
##########################################################################
Constructer in Python
When ever if you declare Object , through that object we will call class methods ,but when we declare constructer inside a class example __init__ by default these will be invoked with out calling those by using Objects. These are called constructers in Python .
##########################################################################
# What does the Join Method do

Join method joins the elements in the List by using the separator which is used Infront of the Join. Please find the output below for reference .

# If `line` was initially a list like this: line = ["apple", "banana", "cherry"] # After `line = "-".join(line)`, it becomes: line = "apple-banana-cherry"
##########################################################################
# What is a Numpy Array

The NumPy (Numeric Python) package helps us manipulate large arrays and matrices of numeric data. To use the NumPy module, we need to import it using: import numpy Arrays A NumPy array is a grid of values. They are similar to lists, except that every element of an array must be the same type. import numpy a = numpy.array([1,2,3,4,5]) print a[1] #2 b = numpy.array([1,2,3,4,5],float) print b[1] #2.0 In the above example, numpy.array() is used to convert a list into a NumPy array. The second argument (float) can be used to set the type of array elements.
##########################################################################

What is 1D,2D and 3D array

1D represents single list of elements.
import numpy as np

# Create a 1D array
numbers = np.array([1, 2, 3, 4, 5])
print(numbers)  # Output: [1 2 3 4 5]

2D represents a data in a matrix like structure 
# Create a 2D array
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix)  # Output:
# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]

3D Represent data in a cube-like structure, with multiple 2D arrays stacked together.
# Create a 3D array
cube = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print(cube)  # Output:
# [[[ 1  2  3]
#   [ 4  5  6]]

#  [[ 7  8  9]
#   [10 11 12]]]

You can access elements using indexing:
numbers[0] : Accesses the first element of the 1D array.
matrix[1, 2] : Accesses the element at row 1, column 2 of the 2D array.
cube[0, 1, 0] : Accesses the element at the first 2D array, second row, first column of the 3D array.

################################################################################
Merging the dictionaries

Using | operator

name1 = {"kelly": 23, "Derick": 14, "John": 7} name2 = {"Ravi": 45, "Mpho": 67} names = name1 | name2 print(names)

Using the merge ( ** ) operator

name1 = {"kelly": 23, "Derick": 14, "John": 7} name2 = {"Ravi": 45, "Mpho": 67} names = {**name1, **name2} print(names)
################################################################################
Counting the Item Occurrences , Writing by using the loops

list1 = ['John','Kelly', 'Peter', 'Moses', 'Peter']
# Create a count variable 
count = 0
for name in list1:
    if name == 'Peter':
       count +=1
print(f'The name Peter appears in the list' f' {count} times.')

################################################################################
Understanding the GLOBAL variables inside the function

numbers = [1, 2, 3] # Global list def modify_list(): numbers.append(4) # Appends to the global list (works without global keyword) def modify_scalar(): global x # Declare x as global x = 10 # Modifies the global x x = 5 # Global scalar variable modify_list() print(numbers) # Output: [1, 2, 3, 4] modify_scalar() print(x) # Output: 10
################################################################################
sys.argv -- How to use this as command line arguments

save the below code into test.py and run the code in python terminal by passing the arguments .

import sys print ("Number of arguments:", len(sys.argv), "arguments") print ("Argument List:", str(sys.argv)) $ python test.py arg1 arg2 arg3 Number of arguments: 4 arguments. Argument List: ['test.py', 'arg1', 'arg2', 'arg3']
################################################################################
Writing output to the JSON file

import json

data = {
    "name": "Bard",
    "skills": ["coding", "writing", "humor"],
    "interests": {"music": "classical", "movies": "sci-fi"}
}

with open("output.json", "w") as f:
    json.dump(data, f, indent=4)

print("Data written to output.json")
#################################################################################
Reading JSON data from the String

json_string = '[{"name": "Jane", "age": 25, "interests": ["music", "books"]}, {"name": "Tom", "age": 40, "job": "doctor"}]'

import json

# Load the JSON string
data = json.loads(json_string)

# Loop through each object
for person in data:
    print(f"Name: {person['name']}")
    print(f"Age: {person['age']}")
    
    # Access and print a specific value based on a key
    if "interests" in person:
        print(f"Interests: {person['interests']}")
    elif "job" in person:
        print(f"Job: {person['job']}")

print("----------")
###################################################################################

Q: Is there an easy way to tell if a variable is immutable or mutable? A: Well…there’s the rule: numbers, strings, booleans, and tuples are immutable, whereas most everything else is mutable (such as lists, sets, and dictionaries). Things can get a little more complicated if you try to determine this at runtime. One possible technique is to pass your variable to the hash built-in. If the passed-in variable is immutable, hash returns a number. If the passed-in variable is mutable, hash fails with a TypeError, which you’d have to code for with some sort of exception-handling code. But, we might be getting ahead of ourselves here.

x = "hello" hash(x) # Returns a hash value (strings are immutable) y = [1, 2, 3] hash(y) # TypeError: unhashable type: 'list' (lists are mutable)

###################################################################################
Python Exception Handling


try: # Code that might raise an exception except ExceptionType1: # Code to handle ExceptionType1 except ExceptionType2: # Code to handle ExceptionType2 ... else: # Code to execute if no exception occurs finally: # Code to execute always, regardless of whether an exception occurs or not

Explanation: try block: Encloses the code that might raise an exception. If no exceptions occur, execution continues normally. except blocks: Handle specific types of exceptions that might be raised in the try block. You can have multiple except blocks to handle different exception types. If an exception occurs, the corresponding except block is executed, and the rest of the try block is skipped. else block (optional): Executes only if no exceptions occur in the try block. Useful for code that should run only when there are no errors. finally block (optional): Always executes, regardless of whether an exception occurs or not. Often used for cleanup tasks, such as closing files or releasing resources.

Example Program
try: num = int(input("Enter a number: ")) result = 10 / num except ValueError: print("Invalid input. Please enter a number.") except ZeroDivisionError: print("Cannot divide by zero.") else: print("The result is:", result) finally: print("This code always executes.")

Use specific exception types in except blocks for better handling. The else block is optional and often used for code that should run only when there are no errors. The finally block is also optional but often used for cleanup tasks, ensuring they're always executed. This structure promotes cleaner and more error-resilient code.
###################################################################################

What is sys.exc_info() in except block of python :

  • This function returns a tuple with three items:   (type, value, traceback).
##########################################################################
try:
 print(100/0) 
 
except:
 print(sys.exc_info()[1] , 'Exception occured') 
 
else:
 print('No exception occurred') 
finally:
 print('Run this block of code always')  


In Python, sys.exc_info()[1] accesses the second item of the tuple returned by the sys.exc_info() function, which represents the current exception being handled.
It's typically an instance of an exception class, such as ValueError, TypeError, ZeroDivisionError, etc.
It holds details about the error, including its message and any relevant data.
If no exception is being handled, sys.exc_info() returns a tuple of three None values.
It's generally preferred to use more specific exception handling techniques with try...except blocks, but sys.exc_info() can be useful in certain debugging or error-handling scenarios.
###################################################################################
How to handle Specific exceptions in Except block 

try:
    num = int(input("Enter a number: "))
    result = 10 / num
except ValueError:
    print("Invalid input. Please enter a number.")
except ZeroDivisionError:
    print("Cannot divide by zero.")
except Exception as e:  # Catch-all for any other unexpected errors
    print("An unexpected error occurred:", e)
else:
    print("The result is:", result)
finally:
    print("This code always executes.")

###################################################################################

Python Decorators 

# Apply multiple decorator on a single function
def InstallDecorator1(func):
 def wrapper():
 print('Please accept terms & conditions...\n')
 func()
 return wrapper
def InstallDecorator2(func):
 def wrapper():
 print('Please enter correct license key...\n')
 return func()
 return wrapper
def InstallDecorator3(func):
 def wrapper():
 print('Please enter partitioning choice...\n')
 return func()
 return wrapper
@InstallDecorator1
@InstallDecorator2
@InstallDecorator3
def InstallLinux():
 print('Linux installation has started \n')
InstallLinux()

@ symbol: The @ symbol is used to apply decorators to the InstallLinux function.
Order of decorators: The order in which decorators are applied is crucial, as they are executed in reverse order. In this case, the order is:
@InstallDecorator3
@InstallDecorator2
@InstallDecorator1

Function Execution:

Calling InstallLinux(): When you call InstallLinux(), the following sequence occurs:
InstallDecorator3: Its wrapper function prints "Please enter partitioning choice..."
InstallDecorator2: Its wrapper function prints "Please enter correct license key..."
InstallDecorator1: Its wrapper function prints "Please accept terms & conditions..."
InstallLinux(): Finally, the original InstallLinux function executes, printing "Linux installation has started".

Output:

Please enter partitioning choice...
Please enter correct license key...
Please accept terms & conditions...
Linux installation has started

###################################################################################

No comments:

Post a Comment

SQL -

 Window Functions -  Window function: pySpark window functions are useful when you want to examine relationships within group of data rather...