Syntax error:
Syntax error occurs when an incorrect statement is written in the code, violating the syntax rules of the programming language.
For instance, if a function is declared incorrectly without a colon, like in the following code:
def function()
print("Hello World")
This code will result in a syntax error and the error console will point out the exact location of the error.
Syntax errors are grammatical programming errors that arise when we do not follow the syntax rules.
A missing bracket in a print statement could also cause a syntax error.
Logical error:
Logical error is one where the program does not crash but produces an incorrect outcome. Let's take an example:
def addition(a,b):
return a-b
result = addition(5,3)
print(result)
The above program works without crashing but produces an incorrect result. Logical errors occur because of incorrect logic, calculation, or decision-making.
Such logical errors are hard to fix because you don't exactly know what causes the error. When you have a large codebase, finding logical errors is an extremely difficult task. To find logical errors, you have to find the problem on your own by finding the relevant parts of your code.
Runtime error:
An error that occurs while a program is executing is called a runtime error.
Let's take an example of dividing two numbers.
First, let's discuss the divide-by-zero error.
Here's an example of code that would produce a runtime error due to a divide by zero:
numerator = 10 denominator = 0 result = numerator/denominator print(result)
This would result in a ZeroDivisionError at runtime.
This is called a divide-by-zero error.
Here is an example of code that would produce a runtime error due to accessing an undefined variable:
x = 5 y = z + 3 print(x) print(y)
In the above code, the variable z has not been defined, so trying to use it in the expression y = z + 3 will result in a NameError at runtime.
We can avoid syntax errors by writing correct syntax.
We can also avoid logical errors by testing our code with different inputs and checking if our program produces the correct result.
But runtime errors are difficult to avoid.
Let's say we want to accept two numbers from users, and it's not our choice to determine if the denominator is zero or not.
In such cases, you will get the divide-by-zero error.
Example:
n = int(input('Enter numerator'))
d = int(input('Enter denominator'))
result = n/d
print(result)
To avoid this, we use something called exception handling.
Exception handling:
To handle exceptions, we need a way to try out a set of code to see if that causes an exception.
For example, in the code below, the only line of code which might cause exception is the
result = n/d
Hence to avoid this, we can first try out this block of code and see if it gives an error.
To do this, we use something called a try block.
What we do is, if we take the code which might cause an exception and we place it inside the try block.
Example:
n = int(input('Enter numerator'))
d = int(input('Enter denominator'))
try:
result = n/d
print(result)
except ZeroDivisionError:
# write the code we want to execute when exception occurs
print('Divide by zero error')
print(result)
Above code prompts the user to enter a numerator and a denominator.
It then attempts to divide the numerator by the denominator.
However, to handle any potential errors that could arise from dividing by zero, the division operation is enclosed in a try block.
If a ZeroDivisionError exception is raised, which occurs when the denominator is zero, the code in the except block is executed.
In this case, the code will print a message saying that there was a divide by zero error.
Finally, the result of the division is printed, whether or not an exception occurred.
Returning multiple values from a function.
Till this point we have learned how to return a single value from a function.
However, we can also make a function return multiple values as well.
Here is how:
# returning multiple values from a function
def circle(r):
area = 3.14*r*r
circumfurence = 2*3.14*r
return area,circumfurence
# calling the function and extracting ghe value
a,c = circle(10)
print(f"Area of a circle is {a}, circumference is {c}")
Passing different data structures to a function:
We cannot just pass a single integer or a string to a function, we can also pass some complex data structure like a list to a function.
Here is an example of passing an entire list to a given function.
def sum(numbers):
total = 0
for number in numbers:
total = total + number
return total
result = sum([1,2,3,4,5])
print(result)
Remove duplicates from a list:
Using a combination of a loop, "in" and append, we can remove duplicate values from a list.
def remove_duplicates(numbers):
# create a new list which does not contain duplicates
new_list =[]
for number in numbers:
if number not in new_list:
new_list.append(number)
return new_list
list = [1,2,3,4,3,4,5,2,1,7,8,9]
result = remove_duplicates(list)
print(result)
A simpler way is by using set()
Take the list, pass it to the set it will remove the duplicate elements.
Then convert the set back into a list:
def remove_duplicates(lst):
return list(set(lst))
Local & global variables.
Global variables:
Global variables are defined outside of any function or block and can be accessed throughout the program.
They have global scope, meaning they can be accessed by any part of the program, including functions and blocks.
Global variables are typically defined at the top level of the program.
To define a global variable, you simply assign a value to a variable outside of any function or block.
Global variables can be accessed and modified by any part of the program unless they are shadowed by a local variable with the same name inside a function.
Local variables:
Local variables are defined within a function or block and can only be accessed within that specific function or block.
They have local scope, meaning they are limited in visibility to the block in which they are defined.
Local variables are typically defined inside functions, loops, or conditional statements.
To define a local variable, you assign a value to a variable inside a function or block.
Local variables can only be accessed and modified within the function or block in which they are defined. They are not visible outside of their scope.
Let's take an example to understand global and local variables.
count = 10 #this is called a global variable
print(count)
def add():
# when we declare a variable inside a function it becomes a local variable
# scope of a global variable is limited to the function, we cannot access
# its value outside the fuction
count = 20
print(count)
add()
Lets take another example
def add():
count = 20
print(count)
print(count)
This wont work because the variable count is local variable and it cannot be accessed outside the variable.
A variable created in one function cannot be accessed in other function either.
Lets create two functions with variable count.
def add():
count =1
print(count)
def sub():
count =2
print(count)
sub()
add()
Accessing global variable inside a function
count = 10
def increment():
print(count)
increment()
This will work because count here is a global variable.
A global variable is accessible everywhere outside as well as inside the function as well.
But now lets try incrementing the value of count inside the function.
count = 10
def increment():
count = count + 1
print(count)
increment()
This gives an error and the error log says, local variable cannot be referenced before assignment.
This does not work because Python thinks that the variable declared inside the function is a local variable.
To access global variable inside a function, we need to tell python that count is a global variable.
We do this using the global keyword.
count = 10
def increment():
global count
count = count + 1
print(count)
increment()
Check if a given string is palindrome:
A palindrome is a string which when reversed gives the exact same output.
The code below explain step by step how a string could be checked for being a palindrome.
To check the first letters and last letters of a string, we first loop through the string in a forward manner and then also loop though it in reverse and compare the letters if they match.
To loop in a reverse manner, we simply use the length of the string and subtract 1 from it every time the loop executes.
If the string is found not to be a palindrome, we set the palindrome_flag to false.
word = "panasonic"
# treat a string like a list
print(word[0])
# get the length of string
l = len(word)
# loop through all the items from the start
print('Printing the string straight')
for i in range(l):
print(word[i])
# loop through the items in reverse
print('Printing the string in reverse')
for i in range(l):
print(word[l-i-1])
#comparing and returning if palindrome
palindrome_flag = True
for i in range(l):
if word[i] != word[l-i-1]:
palindrome_flag = False
break
else:
palindrome_flag = True
if palindrome_flag:
print("The string is a palindrome")
else:
print('The string is not a palindrome')
#converting the above code into a function
def check_palindrome(word):
#get the lenth of the string
l = len(word)
for i in range(l):
if word[i] != word[l-i-1]:
return False
else:
return True
# call the above function
print(check_palindrome("racecar"))
EMI calculator:
#formula to calculate emi is: P x R x (1+R)^N / [(1+R)^N-1]
#P: Principal loan amount = INR 10,000,00
#N: Loan tenure in months = 120 months
#R: Interest rate per month [7.2/12/100] = 0.006
def emi_calculator(principal,rate,time):
#calculate the monthly rate
r = rate/12/100
emi = (principal * r * (1+r)**time) / ((1+r)**time -1 )
return emi
#checking the function
print(emi_calculator(4858900, 8.75, 240))
Recursion:
Recursion in python, in simple term means a function calling itself.
def hello():
print('Hello')
hello()
hello()
How factorial is calculated mathematically:
4! = 4x3x2x1 2! = 2x1 100! = 100x99x98......
Python code to calculate factorial:
def factorial(number):
if number ==1:
return 1
else:
return number * factorial(number-1)
print(factorial(4))
#how the iterations work
#factorial(4) => return 4 * factorial(3)
#factorial(3) => return 3 * factorial(2)
#factorial(2) => return 2 * factorial(1)
#factorial(1) => return 1
Variable length positional arguments:
In some cases we dont know the number of arguments we need to pass to a function.
Example:
A function sum which can accepts any number of numbers to be added.
Hence to create a function that can accept variable number of arguments we use variable length arguments.
Variable length arguments are defined by *args.
When we pass arguments to *args it stores it in a tuple.
A program that can add any numbers passed to it.
def add(*args):
sum = 0
for n in args:
sum = sum+n
print(sum)
add()
add(10)
add(20, 30)
add(40, 50, 60)
Variable length keyword arguments:
A variable length keyword arguments lets you pass any number or arguments along with a keyword rather than the position.
def product(**kwargs):
for key, value in kwargs.items():
print(key+":"+value)
product(name='iphone', price="700")
product(name='iPad', price="400", description="This is an ipad")
Decorators in Python:
In simple words, decorators are a function that take another function and extends its behaviour without altering the code.
Lets take an example to understand this.
def chocolate():
print('chocolate')
chocolate()
When we call this, it simply prints chocolate.
Now lets say I want to add a wrapper to the chocolate which prints wrapper and wrapper before and after chocolate.
To do this, we use a decorator function.
def chocolate():
print('chocolate')
# this decorator function accepts function as argument
def decorator(func):
# inside the decorator function we have another function
def wrapper():
print('Wrapper up side')
# now here I call the func
func()
print('Wrapper down side')
# now the decorator needs to return this wrapper
return wrapper
# now to call the functions, i first call the decorator and then pass chocolate inside it
# then assign the function back to the previous main function i.e chocolate
# finally call the chocolate function
chocolate = decorator(chocolate)
chocolate()
As you can see now the behaviour of the chocolate function is modified.
Hence we can say that decorators simply wrap the function to modify its behaviour.
Another way of using decorator:
In the above code after defining two functions we had to write the last 2 lines to use the decorator.
However, instead of that we can also use a simpler way.
def decorator(func):
# inside the decorator function we have another function
def wrapper():
print('Wrapper up side')
# now here I call the func
func()
print('Wrapper down side')
# now the decorator needs to return this wrapper
return wrapper
@decorator
def chocolate():
print('chocolate')
chocolate()
Reusing decorators:
Decorators can be reused as well.
The decorator we used for wrapping can also be used for another function as well.
lets create another function called cake.
@decorator
def cake():
print('cake')
cake()
Decorating functions which accept an argument:
Lets try adding some argument to the cake function and see if it works.
@decorator
def cake(name):
print('cake'+name)
cake("apple")
This will give us an error, because the wrapper which calls the function does not accept any arguments.
Hence we need to make the func() inside the wrapper accept arguments as well.
def decorator(func):
def wrapper(name):
print('Wrapper up side')
func(name)
print('Wrapper down side')
return wrapper
Now this works.
However when we now try to use the same decorator with chocolate it wont work because we have not passed in name there.
Try doing it:
chocolate()
To avoid this, we pass variable length arguments and variable length keyword arguments to the function instead of simply passing it name.
def decorator(func):
def wrapper(*args, **kwargs):
print('Wrapper up side')
func(*args, **kwargs)
print('Wrapper down side')
return wrapper
@decorator
def chocolate():
print('chocolate')
@decorator
def cake(name):
print('cake'+name)
chocolate()
cake("Mango")
Now both of them work well.
Decorating functions that return a value:
Lets assume we have a function that calculates the total amount of shopping.
On top of it, we want another function that calculates a 50% summer discount on the total value.
Create a function called total that calculates the total price:
def total(price):
# Let's assume there was some code here which calculated the total amount
return price
Now lets create a decorator for it:
def summer_discount_decorator(func):
def wrapper(price):
# take the value inside the total and return
func(price)
return func(price/2)
return wrapper
Entire code:
def summer_discount_decorator(func):
def wrapper(price):
# take the value inside the total and return
func(price)
return func(price/2)
return wrapper
@summer_discount_decorator
def total(price):
# Let's assume there was some code here which calculated the total amount
return price
print(total(20))