Learn Python: Functions and Object Orientation
In this section, we will learn about Function and Object Orientation in detail with examples and how they are useful in real-time programming.
One of the fundamentals of any programming language is that there are often repeated elements. You can cut and paste code from one section to another, but this looks messy! but what happens when you need to update the particular part of the program, for a small part of programs this is unlikely to generate any significant overhead, but as the size and complexity of the program increases, so does the time required to modify and update essentially the same piece of code over and over. Duplication also runs the risk of introducing additional syntactic, logical, and typographical errors. Imagine duplicating a sequence that contained a simple typing mistake would trace every single instance to isolate the problem. This is where
functions solve all your problems. By placing the repeated code into a function you isolate that code sequence and provide easier access to improve the code or isolate bugs. This method of taking repeated pieces of code and placing them into a function is called
abstractions. In general, a certain level of abstraction is always useful it speeds up the programming process reduces the risk of introducing errors, and makes a complex program easier to maintain. We know that Python has 3 basic levels of abstraction.
1. A program can be split into multiple modules. 2. Each module contains multiple statements. 3. Each statement operates on objects
Function Definition and Execution
The general format for creating new functions is as follows:
def NAME(ARG1[,...]): STATEMENT BLOCK [return VALUE]
NAME value should follow the same rules as object names:
- Functions must begin with an underscore or letter and may contain any combination of letters, digits, or underscores. You cannot use any other punctuation characters.
- Functions are case sensitive, combine and Combine are two different function names. However, you should avoid creating functions of the same name but in different cases.
- You cannot use a reserved word for a function name but remember the case-sensitive rule.
The following example is a simple function that calculates the area of a circle based on the radius supplied:
def area(radius): area = 3.141*(pow(radius, 2)) return area To use the area function, just call with an argument: print(area(2)) which returns 12.564
Note the precision in the result is identical to the precision you use for the value of pi. If you supply a value with higher decimal precision, the return value is also of higher precision. The statement
print(area(3.141592654)) which returns 31.0004274319
Finally, unlike other languages, functions within Python can be defined on the fly, you can create a function anywhere within the normal execution of a program. The following code is perfectly valid:
if (message): def function(): print("Hello") else: def function(): print("Goodbye")
It opens up all sorts of possibilities, especially when it comes to the development of objects and methods. The scoping, argument definition, return values, and the methods for calling functions all have extended feature sets within the Python language so let's look at each feature individually.
Python uses the concepts of
namespaces in order to store the information about objects and their location within an application. Namespaces are specific to a particular level of abstraction. There are individual namespaces for the functions and modules and there's a special namespace for the built-in functions and objects. Scoping within Python follows these basic rules:
1) Each Module has its own scope. This means that multiple modules have their own scope and therefore their own namespaces. 2) The enclosing module for a function is called the global scope. This is the location of objects created at the top level of a module file. 3) New function definitions create a new scopes; the scopes are unique to the function. 4) New objects are assigned to the local scope unless declared otherwise. Any variable or function that is defined will be a member of the local scope unless you declare it global with the global keyword. 5) Arguments to a function are defined within the local function scope.
The scoping rules mean that objects created within a function do not interfere with identically named objects in the module(global) or built-in namespaces. Our
area function is a good example:
def area(radius): area = 3.141*(pow(radius, 2)) return area
function is global, because, it's defined within the top-level of the module. The
area variable is defined within the local scope of the
area function. This means that although they have the same name, they do not interfere with each other.
Making Objects Global
There are occasions when you want to assign the value of a variable within a function, but you have it modify the global variable instead of creating a new one. The traditional method for this is to predeclare the variable before the function is called:
name = 'unknown' def set_defaults(): name = 'Python' set defaults() print(name)
However, this doesn't work in Python because the moment you assign the
name variable within the function, Python creates a new variable within the local scope. To get around this, you need to use the
global keyword to define which variables you want to use in this way. The
global keyword tells Python to create a local alias to the global variable. For example, if you modify the preceding script.
name = 'unknown' def set_defaults(): global name name = 'Python' set defaults() print(name)
This function works what you expect, it prints Python. The
global keyword also creates objects within the global namespace if they don't already exist, as in the following example.
def set_defaults(): global names, address name = 'Python' address = 'Python@py.com' set_defaults() print('Name : ', name, 'Address : ', address) Output: Name : Python Address : Python@py.com
Object Orientation programming is a mechanism that allows you to create intelligent variables called
objects that can be used to store complex structures that not only hold data but also know what functions and operations can be performed on them. The system work through the creation of a
class defines the format and structure of individual objects called
instances and defines the functions that operate on those objects.
Creating a class
Creating a new class in Python requires the class statement, which works like any other block definition in Python, everything contained within the
class block becomes a part of the class you're defining. The format for creating a new class looks like this:
class CLASSNAME([CLASS_PARENT, ...]): [STD_ATTRIBUTES] . . . def METHOD(self, [METHODARGS]): . . .
CLASSNAME is the name of the class that you want to create. Python uses the same basic rule for classes as for any other object. However, as a general rule. user-defined class names are often in title case to distinguish them from the standard library class supplied with Python. The parentheses following the class are optional and are used only for specifying any classes from which you inherit attributes. The
STD_ATTRIBUTES is the default attributes that you want to apply to all instances of this class. These are given static values at this point, unlike the attributes that you might set during the initialization. For example, you can create the Account class as follows:
class Account: account type = 'Basic' def __init__(self, name, balance): self.name = name self.balance = balance def deposit(self, value): self.balance += value def withdraw(self, value): self.balance -= vlaue To create a new object based on our new class, you call the class as if it is a function: bank = Account('HSBC', 2000)
Class methods are in fact just functions that have been defined within a scope of a given class. The only difference between a class method and an ordinary function is that the first argument to any class method is the object on which it is operating. For example, when you call the
deposit() method on an instance,
Python actually calls the
deposit() function within the
Account class, supplying the object as the first argument (typically
self) and the argument you supplied to the deposit function as the second argument:
This enables you to access the object attributes and update their values from within the function. Without self-argument, you'd never be able to modify the object. You can see this more clearly in the following simplified class and method definition of the
class Account: account_type = 'Basic' def __init__(self, name, balance): self.name = name self.balance = balance def deposit(self, value): self.balance += value
The __init__() function is the special name you should use within a class for the constructor. This function is called when you create a new object based on this class. In below example
def __init__(self, name, balance): self.name = name self.balance = balance
The method accepts two arguments: the name of the account you're creating and the balance of the account. These are used to initialize the values of the object's attributes. What this actually does is call:
bank = Account.__init__('HSBC', 2000)
The new variable, the bank is an object or an instance of the
Account class. You can get the balance of the account by accessing the
All class instances have a reference count for the number of times they have been referenced: this count can include, for example, each name that refers to that instance, each time the instance is included as part of a list, tuple, or dictionary and so on. When the reference count reaches 0, the instance is automatically destroyed, freeing up the memory used to hold the object data.
If you want to define a custom sequence for destroying an object(a destructor function) you need to define the __del__() method. Python automatically calls the __del__() method when an object needs to be destroyed. All objects inherit a built-in __del__() method if the class does not define its own. Because of this, most basic objects, such as the example created here, don't require the __del__() method.
The Parentheses following a class name are used by Python to identify the classes from which you inherit attributes and methods. As an example below where two classes are defined
Bank Account, each of which inherits artifacts from the base class
class BankAccount(Account): account_type = 'Bank Account' def __init__(self, name, balance): self.name = name self.balance = balance def __init__(self, name, balance, accno, sortcode, bankname): Account.__init(self, name, balance) self.accno = accno self.sortcode = sortcode self.bankname = bankname class CreditCard(Account): account_type = 'Credit Card' def __init(self, name, balance, accno, expiry, limit, rate): Account.__init__(self, name, balance) self.accno = accno self.expiry = expiry self.limit = limit self.rate = rate def withdraw(self, amount): if abs(self.balance-amount)
Note that the
withdraw() method within the
CreditCard the class overrides the default method defined in the
Account class. Note also that an exception was added that is raised when you try to withdraw money beyond your limit. If you now create a
CreditCard instance, notice that you can use the
>>> visa = CreditCard('HSBC', -1000, '123456789', '02/99', 8000, 18) >>> visa.deposits(500) >>> visa.balance -500 >>> visa.withdraw(2000) >>> visa.balance -2500
One thing to note is that when you create an instance of a class that inherits from another class, the constructor for the new class does not automatically call the constructor method for the base class, hence the need for the following line in the constructor for both the
Account.__init__(self, name, balance)
This directly calls the constructor for the base class during the name and balance supplied to the class constructor to initialize an instance within the current class. Note that you don't have to call the base class constructor immediately if you don't want to, but it's probably a good idea.