2 Functions and Methods
2.1 List slicing
Subset of lists can be accessed nicely with something called as slicing. Here is how slicing works.::
list[*start*:*end*:*step*]
So if you have a list ::
digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
you want a subset of this list which starts at index 2 , till index less than 8 and at steps of two.::
>>> digits[2:8:2]
[2, 4, 6]
>>> digits[2:8:3] # start at 2 end at 8 (excluded) at step of 3
[2, 5]
>>> digits[2:8] #start at 2 end at 8 default step of 1
[2, 3, 4, 5, 6, 7]
Make note of these default values
* If step is not given , it is taken as 1 by default.
* if start is not given it is taken at 0 by default
* if end is not given it is taken as end of string
So here are some examples of default values for start, end::
>>> digits[:5] # take first 5
[0, 1, 2, 3, 4]
>>> digits[4:] # drop first 4
[4, 5, 6, 7, 8, 9]
>>> digits[::2] # take alternate starting at 0
[0, 2, 4, 6, 8]
>>> digits[::-1] # reverse the list
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
It is posible to write complicated list slicing expressions using combination of -ve numbers and default values. But it makes the code cryptic. So it is advised to make use of standard list slice as shown above. These standard slices will make your code concise but same time readable.
2.2 Functions
2.2.1 Types and Converting
As we know variable is nothing but just a name. So if we want to know what is it that is stored with the given name?::
>>> name = "rupali"
>>> type(name)
str
>>> numbers = [1, 2, 3]
>>> type(numbers)
list
>>> point = (0, 0, 1)
>>> type(point)
tuple
>>> stock = {"name":"IBM", "open":123, "high":126, "low": 120, "close":123.5}
>>> type(stock)
dict
>>> type(1)
int
>>> type(1.2)
float
str function can be used to convert other datatypes into string.::
>>> str("23")
23
int can be used to convert string or float to integer::
>>> int("42")
42
list can be used to make a list from string (for that matter from any collection)::
>>> list("1234")
["1","2","3","4"]
>>> r5 = range(5)
>>> list(r5)
[0,1,2,3,4]
max function can find maximum value from collection like list or tuple.::
>>> max([23, 12, 34, 13, 5, 6, 12, 35])
35
min function can find minimum value from list or tuple::
>>> min([23, 12, 34, 13, 5, 6, 12, 35])
5
sum function sums all items from a list or tuple::
>>> sum([1, 1, 1, 1])
4
2.2.2 More built in functions
We have seen max, min, sum, int, str, float, type functions earlier. Now let us see some more built in functions. Usually programming in any language starts with hello world. It is time to make python’s hello world.::
>>> print("Hello World")
'Hello World'
>>> print("Hello", ",", "Let's", "print", 1, 2)
Hello , Let's print 1 2
Next we will take input from user::
>>> x = input("Input value for x:")
Input value for x:3
>>> print(x)
3
We have already seen that functions sum, max, min operate on lists. Here is another function which works on a list. sorted takes a list or tuple and returns sorted list.::
>>> sorted([3,2,4,1,1])
[1, 1, 2, 3, 4]
>>> sorted([3,2,4,1,1], reverse=True)# this is optional argument for sorted
[4, 3, 2, 1, 1]
2.2.3 Creating custom functions
So far we have used statements. Putting few statements together for frequent use is doen through function. Functions allow us to make black box abstraction. Its like a box which has got some inputs and it does something on inputs and user justs the output back. For example let’s make a balck box for computing square::
def square(x):
return x*x
The moment we define function as given above, python creates some blackbox for the code inside it. it stores that box in python’s memory. It creates a name square in current namespace. And links this name to the black box. This is how we call this function.::
>>> square # this is not calling, it is just refering to name, square!
<function __main__.square(x)>
>>> square(4) ## <--- this is how you call the black box functionality.
16
Let’s look closely at the syntax::
def sumofsquares(a, b): # this is where function defination starts, has to end with :
a2 = square(a) # next line must be indented (4 spaces as a convention)
b2 = square(b) # all lines in this code block have same indentation.
return a1 + b2 # finally return statement
sumofsquares(2, 3) ## here indentation comes back to original , function is over this line is outside function.
As function must have atleast one statement. There is empty statement to make empty function.::
def donothing():
pass
A function can be defined without return statement too.::
def say_hello(name):
print("Hello ", name)
Make a note what happens if save the result of function in a variable.::
>>> sqr5 = square(5)
>>> print(sqr5)
25
>>> hello = say_hello("python")
Hello python
>>> print(hello)
None
2.2.4 Calling Function Vs Function
One has to understand difference between calling a function, defining a function and just reffering a function. When we call a function, the arguments can be litterals or variables. While when we define a function, arguments defined can be only variable names. It can not be litteral while defining a function. For example this would be a mistake::
def add(2, 3):#incorrect
return 2+3
def add("a", "b"):#incorrect
return a + b
def add("a", "b"):#incorrect
return "a" + "b"
Correct way to define this add function is as shown below.::
def add(a, b):
return a + b
While calling a function::
add(2, 3) #correct
add(a, b) #incorrect if a and b are are not predefined.
a = 2
b = 3
2.2.5 Nested function call
When function calls are nested, inner most function is evaluated first then next inner most, then next … like this till all nested function calls are over.::
def square(x):
return x*x
def double(x):
return 2*x
def addone(x):
return x+1
Following line will get executed as given below::
addone(double(square(3)))
addone(double(9)) # square is evaluated
addone(18) # double is evaluated
19 # addone is evaluated
2.2.6 Namespace and Functions
Everytime we call a function. With entry to function, it creates it’s own name space. And in that name space it creates names for function parameters. These names point to same location from where the pararameters are passed.
2.2.6.1 Scope of Variables
Variables defined at top level of program where main script starts are global variables. At this level globals and locals are same. Once we call a function, as soon as function execution starts, all variables created there are local to that function. Variables passed as argument to function are passed by reference. it means they refer to same object, which is passed to function. Only difference is that it is called by a different name inside the function. This is because with every function call a new namespace is created. And variables in function reside in this newly created namespace. As soon as function call is over, namespace created with function call is also deleted, so all variables in it.
Scope rules
- Any name the statement refers, is looked in local scope first.
- if name is not there is local scope, global scope is chacked for reading
- if local name exists, then there is no way we can get global with same name
2.2.7 Styleguide for writing functions
A mathematical function that computes result but prints it, is not reusable in nested calls or to save the returned values in variables!::
def twice(x):
print(2*x)
fourtimes7 = twice(twice(7)) # this will fail.
a, b = 2, 3
def add():
return a+b # does not take a, b as parameter of function, not reusable!
n = 5
def power_(x):
return x**n # not good practice, n is taken directly from global context.
Some guidelines to remember
- A reusable function is perfect black box. it works only on given inputs.
- A reusable function returns the computed value
- A reusable function does not make use of gloabl variables.
- A reusable function takes all that is required as argument.
Style guide
- Give meaningful names to functions
- Give meaningful names to variables
- Write code to be read by humans first then by machine.
2.2.8 Function’s Arguments
Have a look at the function::
def say_hello(name, greeting):
print(greeting, name + "!")
The right way to call this function is
>>> say_hello("Vikrant", "Hello")
Hello Vikrant!
But the user mistook the order of argumets!
>>> say_hello("Hello", "Vikrant")
Vikrant Hello!
Which is obviously not acceptable. It could have been some numeric computaion. If the order of arguments is wrong, we will likely get wrong results.
def cylinder_volume(radius, height):
return 3.14*radius**2*height
The result we get by using correct order of arguments is::
>>> cylinder_volume(1.0, 2.0)
6.28
and if we give arguments in wrong order? it is way different from answer when we give correct order::
>>> cylinder_volume(2, 1)
12.56
How do we solve this? Don’t worry python has a solution for this. when in confusion use named arguments.::
>>> cylinder_volume(radius=1, height=2)
6.28
>>> say_hello(greeting="Namaskar", name="Vikrant")
Namaskar Vikrant!
Also sometimes we feel need for default argumets. For example, if no greeting is specified take it “hello” by default!::
def say_hello(name, greeting="Hello"):
print(name, greeting + "!")
>>> say_hello("Vikrant")
Hello Vikrant!
>>> say_hello("Vikrant", greeting="Namaskar")
Namaskar Vikrant!
2.2.9 Passing Functions As Arguments
Functions are nothing different from integers and other datatypes. Just like integers can be stored inside a variable, same way function can also be! in fact they are variables stored inside a variable which has name as function name::
def foo:
print("foobar!")
If we examine this variable foo::
>>> foo
<function __main__.foo()>
>>> bar = foo
>>> bar
<function __main__.foo()>
>> bar()
foobar!
This means just like other variables, one should be able to pass on functions as arguments to another functions. One way of looking at pythonish way of programming is resuse. Never rewrite same code at two places in same program.::
def square(x):
return x*x
def sumofsquares(x, y):
return square(x) + square(y)
def cube(x):
return x**3
def sumofcubes(x, y):
return cube(x) + cube(y)
If you look closely functions sumofsquares and sumofcubes are actually same pieces of code except the functions square and cube used in it! This is perfect example of code repeatation. We can avoid it by writing a fucntion which abstracts out the core idea of sumof::
def sumof(x, y , func):
return func(x) + func(y)
With this function we can do the job of sumofsquares using::
>>> sumof(2, 3, square)
13
With the same function we can do the job of sumofcubes::
>>> sumof(2, 3, cube)
35
This idea of passing functions as argument is so useful that many python builtin functions make use of it. For example max, min, sorted these functions have a named parameter. We know normal working of max function::
>>> max([3, 23, 4, 2])
23
But what about working with some complex task like, finding max by some logic.::
>>> words = ["one", "two", "three", "four", "five", "six"]
>>> max(words)
'two'
This is as we know by logic of ASCII order. but what if we want to find a word with maximum length.::
>>> max(words, key=len) # tell max.. how to find max... max by len!
"three"
Suppose we have some records as given below. A record has name, value and gain::
records = [
("TATA", 200.0, 5.5),
("INFY", 2000.0, -5),
("RELIANCE", 1505.5, 50.0),
("HCL", 1200, 70.5)
]
How to find a record that has max value?::
def get_value(r):
return r[1]
max(records, key=get_value)
("INFY", 2000.0, -5)
Similarly how to find a record that has max gain?::
def get_gain(r):
return r[2]
max(records, key=get_gain)
("HCL", 1200, 70.5)
2.3 Methods
Methods are functions inside an object. Methods can manipulate date inside the object.
2.3.1 String methods
Till now we used built in functions of python. Now lets see some useful methods from some useful datatyeps. Methods are tied to specific opjects. That way functions are independent. Let us say we have a string stored in a variable sentence.::
>>> sentence = "These Are Few Wise Words"
We have few methods in string object that allows us to do some checks::
>>> sentence.startswith("These")
True
>>> sentence.startswith("This")
False
>>> sentence.endswith("Words")
True
>>> sentence.endswith("nums")
False
>>> sentence.isupper()
False
>>> sentence.islower()
False
>>> "alllower".islower() #can be called with litterals too.
True
>>> sentence.istitle()
True
>>> sentence.isalpha() # does it have only alphabets, no space too
False
>>> "onlyletters".isalpha()
True
>>> sentence.isalnum() # check if string has only letters and numbers
False
>>> "user123".isalnum()
True
In addition to checks there are few useful transformation methods in string object.::
>>> "hello".capitalize()
'Hello'
>>> sentence.upper()
'THESE ARE FEW WISE WORDS'
>>> sentence.lower()
'these are few wise words'
>>> "hello world".title()
'Hello World'
>>> sentence.rjust(50)
' These Are Few Wise Words'
>>> sentence.ljust(50)
'These Are Few Wise Words '
>>> sentence.center(50)
' These Are Few Wise Words '
>>> sentence.replace("Wise", "Foolish")
'These Are Few Foolish Words'
These two transform methods are most widely used with string processing.::
>>> sentence.split()
['These', 'Are', 'Few', 'Wise', 'Words']
>>> words = sentence.split()
>>> "_".join(words)
'These_Are_Few_Wise_Words'
>>> " ".join(words)
'These Are Few Wise Words'
>>> "-".join(words)
'These-Are-Few-Wise-Words'
Here are some methods that allow us to remove trainling spaces from a string.::
>>> " hello this has spaces to left".lstrip()
'hello this has spaces to left'
>>> 'and this has spaces to right '.rstrip()
'and this has spaces to right'
>>> ' this has spaces on both sides '.strip()
'this has spaces on both sides'
2.3.2 Method chaining
Have a look at chain of methods called on a string.::
sentence = " hello method chaining! "
sentence.strip().split()[-1]
---->---->---->----->----->methods will get executed in this order
2.3.3 List methods
A lists has methods to find items in it.::
>>> nums = [1, 2, 3, 4]
>>> nums.index(1)
0
>>> nums.count(2)
1
>>> nums = [1, 2, 2, 3, 3, 3]
>>> nums.count(2)
2
It has various methods to add new items to it.::
>>> empty = []
>>> empty.append(1)
>>> empty
[1]
>>> empty.append(1)
>>> empty
[1, 1]
>>> empty.insert(0, 23) # insert 23 at location 0
>>> empty
[23, 1, 1]
>>> empty.extend([0, 0, 0])
>>> empty
[23, 1, 1, 0, 0, 0]
It has various methods to remove items from it.::
>>> nums = [1, 2, 3]
>>> nums.remove(2)
>>> nums
[1, 3]
>>> nums.pop() # removes last item
3
>>> nums
[1]
>>> digits = [0, 1, 2, 3, 4, 5, 6]
>>> digits.pop(2) # removes item at location 2
2
>>> digits
[0, 1, 3, 4, 5, 6]
>>> digits.clear() # removes all elements!
>>> digits
[]
and some other manipulations::
>>> nums = [ 3, 2, 4, 1]
>>> nums.sort() # sorts list inplace
>>> nums
[1, 2, 3, 4]
>>> nums.reverse() # reverse inplace
>>> nums
[4, 3, 2, 1]
>>> nums.copy() ### returns a copy of list ..just as nums[:]
[4, 3, 2, 1]