Wednesday, February 28, 2024

Understanding the Purpose of if name == 'main' in Python Scripts

 The __name__ == "__main__" construct in Python is a common pattern used to determine whether a Python script is being run as the main program or if it is being imported as a module into another script. This check is often used to control the execution of code that should only run when the script is executed directly.


We have two .py files.

File 1: sample.py


print('This is sample.py code')


def print_sample_text():
print('This is a sample text')


# __name__ is in-built variable
if __name__ == "__main__":
print('Sample.py is executed directly')
else:
print('Sample.py is imported')
print(f"Its name is: {__name__}")

File 2: demo.py
import sample
sample.print_sample_text()

if __name__ == "__main__":
print('demo.py is executed directly')
else:
print('demo.py is imported')
print(f"Its name is: {__name__}")

Now, if we run sample.py, output will be like:

This is sample.py code
Sample.py is executed directly

If we run demo.py, output will be like:
This is sample.py code
Sample.py is imported
Its name is: sample
This is a sample text
demo.py is executed directly

Now, notice the highlighted line. It should be executed 
only when we are running sample.py. But it is getting executed 
while import. Lets move this to the if block.

# print('This is sample.py code')


def print_sample_text():
print('This is a sample text')


# __name__ is in-built variable
if __name__ == "__main__":
print('Sample.py is executed directly')
print('This is sample.py code')
else:
print('Sample.py is imported')
print(f"Its name is: {__name__}")

Now, the output is:
Sample.py is imported
Its name is: sample
This is a sample text
demo.py is executed directly


Random module and String module

Example 1: 


import random,string


list_of_alphabets = [letter
for letter in string.ascii_letters]
random.shuffle(list_of_alphabets)
print(list_of_alphabets)
Output:
['Y', 'M', 'b', 'F', 'X', 'r', 'm', 'J', 'w', 'o', 
'Z', 'U', 'g', 'l', 'c', 'f', 'A', 'B', 'N', 'G', 'L', 'V', 'h', 
'n', 'I', 'T', 'x', 'y', 'E', 'H', 'D', 'q', 'd', 'O', 't', 'R', 
'S', 's', 'v', 'P', 'e', 'Q', 'k', 'p', 'i', 'W', 'K', 'C', 'j', 'z',
 'a', 'u']

Example 2:

import random
import string


def generate_random_password(length=12):
if length < 8 or length > 16:
raise ValueError("Password length must be between 8 and 16 characters")

password = []

# Combine letters and symbols
list_of_chars = [letter for letter in string.ascii_letters]
list_of_symbols = [symbol
for symbol in string.punctuation]
list_of_chars.extend(list_of_symbols)

# Shuffle the characters
random.shuffle(list_of_chars)

# Select characters for the password
for _ in range(length):
password.append(
random.choice(list_of_chars))

return ''.join(password)


# Generate a random password of length between 8 and 16
random_password = generate_random_password(random.randint(8, 16))
print(random_password)

Output:
x#=K@_&p.D

Access and modify global variable

 # Trying to change global variable

my_surname = 'Chakraborti'


def change_my_surname():
my_surname = 'Chakraborty'


print(f"My Surname before change: {my_surname}")
# Call change my surname function
change_my_surname()
print(f"My Surname after change: {my_surname}")

Output:
My Surname before change: Chakraborti
My Surname after change: Chakraborti

The above code could not change the my_surname (global variable). 
Actually in the change_my_surname() function,
it is creating a local variable.
This can be verified just writing a print statement.

# Access global variable
my_surname = 'Chakraborti'



def change_my_surname():
    my_surname = 'Chakraborty'
print(f"Changing surname to: {my_surname}")


print(f"My Surname before change: {my_surname}")
# Call change my surname function
change_my_surname()
print(f"My Surname after change: {my_surname}")
Output:
My Surname before change: Chakraborti
Changing surname to: Chakraborty
My Surname after change: Chakraborti

However, inside function, we can access the value.
# Access global variable
my_surname = 'Chakraborti'



def change_my_surname():
print(f"Current Surname: {my_surname}")


change_my_surname()

Output:
Current Surname: Chakraborti
Now to change the global variable,

# Change global variable
my_surname = 'Chakraborti'

def change_my_surname():
global my_surname
my_surname =
'Chakraborty'
print(f"Changing surname to: {my_surname}")


print(f"My Surname before change: {my_surname}")
# Call change my surname function
change_my_surname()
print(f"My Surname after change: {my_surname}")
Output:
My Surname before change: Chakraborti
Changing surname to: Chakraborty
My Surname after change: Chakraborty

Combine related information using zip function

Example 1:

 # Example with three lists

names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

# Converting the result to a list for printing
# Example with three lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

# Converting the result to a list for printing
result_list = list(combined)

# Output
print(result_list)
Output:
[('Alice', 25, 'New York'), ('Bob', 30, 'San Francisco'), 
('Charlie', 22, 'Los Angeles')]

Modified Example 1:

# Example with three lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

# Converting the result to a list for printing
# Example with three lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

for emp in combined:
print(f"Employee name: {emp[0]},
Employee Age: {emp[1]} and Work location is {emp[2]}")

Output:
Employee name: Alice, Employee Age: 25 and Work location is New York
Employee name: Bob, Employee Age: 30 and Work location is San Francisco
Employee name: Charlie, Employee Age: 22 and Work location is Los Angeles

**kwargs to handle any number of keyword arguments

 def my_grocery_list(**kwargs):

    print(kwargs)
print(type(kwargs))
for item in kwargs:
print(f"{kwargs[item]} {item}")

my_grocery_list(toothpaste='Colgate',toothbrush='Colgate',
num_of_toothbrush=2)

Output:

{'toothpaste': 'Colgate', 'toothbrush': 'Colgate',
'num_of_toothbrush': 2}
<class 'dict'> Colgate toothpaste Colgate toothbrush 2 num_of_toothbrush

*args to accept any number of positional arguments

 Consider the below case:

We need to all the sum all the inputs. Number of inputs are unknown.

sum(5,8,9)
The above will not work. sum() takes at most 2 arguments (3 given).

But, sum() can calculate the sum of Iterable i.e. means List, Tuple etc.

sum([5,8,9])
or
sum((5,8,9))
* means any number of arguments.
def sum_of_nums(*args):
print(args) -> o/p: (3, 8, 8, 190)
print(type(args)) -> <class 'tuple'>
return sum(args)


print(sum_of_nums(3, 8, 8, 190))

How to access Nested function from outside?

def func_a():
print('Task triggered for function A')

def func_b():
print('Task triggered for function B')

In this case, we cannot call func__b from outside. We can only access 
func_b() from inside of func_a().

def func_a():
print('Task triggered for function A')

def func_b():
print('Task triggered for function B')

func_b()
func_a()

To overcome this, we can return func_b as shown below:
def func_a():
print('Task triggered for function A')

def func_b():
print('Task triggered for function B')

# Notice, I am only providing the function name
return func_b


task_b = func_a()
task_b()