Introduction to Functions:

Functions is one of the most basic levels of reusing code in Python.

Functions is a block of code which only runs when it is called. It is a useful block that groups together a set of statements so they can be run more than once. They can also let us specify parameters that can serve as inputs to the functions.

Function Declaration :

	
	def name_of_function(arg1, arg2):
		'''
		This is function's Document String (docstring) area. When we call help() on our function it will be printed out.	
		'''
		# function Body
		
		# Return result
	

Calling a Function with () :

	
	name_of_function(arg1, arg2):
	
	name_of_function() 	# 
	

!!! Common Mistake !!! - Below code is not correct :

	
	def check_even_list(num_list):
	
		# Go through each number
		
		for number in num_list:	
		
			# Once we get a "hit" on an even number, we return True
			
			if number % 2 == 0:		
			
				return True
				
			# This is WRONG! This returns False at the very first odd number!	
			
			# It doesn't end up checking the other numbers in the list!	
			
			else:			
			
				return False
				
	# UH OH! It is returning False after hitting the first 1
	
	check_even_list([1,2,3])
	

!!! Correct Approach !!! We need to initiate a return False AFTER running through the entire loop.

	
	def check_even_list(num_list):
	
		# Go through each number
		
		for number in num_list:
		
			# Once we get a "hit" on an even number, we return True
			
			if number % 2 == 0:
			
				return True
				
			# Don't do anything if its not even
			
			else:
			
				pass
				
		# Notice the indentation! This ensures we run through the entire for loop 
		
		return False

	check_even_list([1,2,3])		# True
	

How to shuffle a list in Python.

	
	example = [1,2,3,4,5]
	
	from random import shuffle
	
	print(shuffle(example))
	

Nested Statements and Scope.

It is important to understand how Python deals with the variable names we assign. When we create a variable name in Python the name is stored in a name-space. Variable names also have a scope, the scope determines the visibility of that variable name to other parts of our code.

	
	x =  40

	def getValue():
		x = 2
		return x

	print(x)		# 40
	
	print(getValue())		#2
	

How does Python know which x** we are referring to in our code? This is where the idea of scope comes in. Python has a set of rules it follows to decide what variables we are referencing in our code.

The idea of scope can be described by 3 general rules.


LEGB Rule :

Local

	
	# x is local here

	square = lambda x : x **2
	

Enclosing function locals

	
	x = 'This is a global variable'

	def greet():
		# Enclosing function
		x = 'Sammy'
		
		def hello():
			print('Hello '+x)
		
		hello()

	greet()  		# Hello Sammy => because the hello() function was enclosed inside of the greet function!
	

Global

	
	x = 'This is a global variable'

	def greet():
		x = 'Sammy'
		.
		.
	print(x)		# This is a global variable
	

Built-in : These are the built-in function names in Python. We should refain from using these name as our variable name.

	
	len
	
	sort
	

Local Variables

When we declare variables inside a function definition, they are not related in any way to other variables with the same names used outside the function - i.e. variable names are local to the function. This is called the scope of the variable. All variables have the scope of the block they are declared in starting from the point of definition of the name.

	
	x = "Sameer"

	def func(x):
		print('x is', x)							#  x is Sameer
		x = "Ramesh"
		print('Changed local variable x to :', x)	#  Changed local variable x to : Ramesh

	func(x)
	print('x is still : ', x)						#  x is still : Sameer
	

The global statement :

If you want to assign a value to a name defined at the top level of the program (i.e. not inside any kind of scope such as functions or classes), then you have to tell Python that the name is not local, but it is global. We do this using the global statement. It is impossible to assign a value to a variable defined outside a function without the global statement.

The change is reflected when we use the value of x in the main block.

	
	x = "Sameer"

	def func(x):
		global x
		print('This function is now using the global x!')
		print('Because of global x is: ', x)
		x = "Ramesh"
		print('Ran func(), changed global x to : ', x)

	print('Before calling func(), x is: ', x)
	func()
	print('Value of x (outside of func()) is: ', x)
	
	o/p : 
	Before calling func(), x is: Sameer
	This function is now using the global x!
	Because of global x is: Sameer
	Ran func(), changed global x to : Ramesh
	Value of x (outside of func()) is: Ramesh