Object-oriented programming is a mechanism that allows you to create intelligent variables-called objects-that can be used to store complex structures that not on them. The system works through the creation of a class. A class defines the format and structure of individual objects-called instances- and defines the functions that operate on those objects.
Classes can also be organized into a tree structure so that there are generic classes and within the generic classes, there are specific classes for modeling other structures. Classes can also inherit methods from their parent classes, which promotes code reuse by allowing you to define a method only once, no matter how many different classes rely on its abilities.
For example, imagine that you are creating a system to manage your bank and credit card accounts. You create a base class called Account that holds two pieces of information: the account name and its current balance. The class definition also includes two methods, one to deposit funds into the account, which automatically updates the balance, and a similar method for withdrawing money from the account.
This base Account class on its own is not enough to hold specific information about an account. You’re also going to create a subclass called BankAccount that inherits the attributes and methods of Account, but includes new attributes to hold the bank account number, sort code, and bank name You can still deposit and withdraw funds using the methods defined in the Account class, but you don’t have to recreate them for the BankAccount class.
In addition, you can create a Credit Card class that also inherits from the Account class. The Credit Card class includes attributes for the account number, expiration date, credit limit, and interest rate. The add interest() method updates the account’s balance by calculating the interest on the account for the previous period.
Let’s look at the specifics of creating these classes as you learn how to create classes. objects, and methods using Python.
Python uses objects for all of its internal data types so you should already be familiar a with how to create different objects and use the information and methods that apply to them. In this chapter you’ll see how to create new classes so that you can model your own classes and objects, and you’ll learn about the ways in which you can extend the classes you create to operate with the built-in functions and operators used by Python.
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 rules 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 classes supplied with Python. The parentheses following the class are optional and are used only for specifying any classes from which you inherit attributes. See the section “Class Inheritance” later in this chapter for more information.
The STD_ ATTRIBUTES are the default attributes that you want applied to all instances of this class. These are given static values at this point, unlike the attributes that you might set during the initialization. We’ll look in more detail at the class methods shortly.
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 - = value
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 the 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 the 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 Account class:
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 The method can accept arguments using any of the forms described in Chapter 4. In our example
def__init__ (self, name, balance): self.neme = 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, bank, is an object or an instance of the Account class. You can get the balance of the account by accessing the balance attribute.
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) (perhaps also to dereference other objects or log the instance deletion), 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 __de;__ () method.
However, be aware that the call to __del__() cannot be relied on in situations where destroying the object requires the closing of files, network connections, or releasing of other system resources.
Python also supports a number of special methods that provide the necessary hooks for interfacing to the built-in functions and operators employed within the Python interpreter. These methods all follow the same basic model as __init__() and __del__(), using the leading and trailing double underscore characters. For example, you can add support for adding two accounts together using the standard + operator by defining add 0 method within your class.
The full list of the special methods that Python supports is shown in Table 6-1. This table lists the basic methods that you can define within a given class for the most basic operations. Nearly all of these methods should be defined in an object class, especially if you plan to release the class to the public and they’re appropriate for the class you are creating.
|_init__(self [,args])||Called when creating a new instance of a class.|
|__del__(self)||Called when an instance is destroyed.|
|__repr__(self)||Called when the repr() function or backtick operator is employed-should return a string representation of the object that is compatible with eval() to recreate the object.|
|__str__(self)||Called when the str() function is called-should return an informal representation of the string.|
|__cmp__(self, other)||Called when comparing two objects, should return a negative value when self is logically smaller than other, zero when the two objects are logically equivalent and a positive number when self is logically greater than other.|
|__hash__(self)||Called when computing a hash value–should return a 32-bit hash index.|
|__nonzero__(self)||Should return 0 when self if logically false or 1 when self is logically true.|
|__getattr__(self, name)||Called when self.name is used, should return the value of the attribute name.|
|__setattr__(self, name, value)||Called when self.name = value is used, should set the value of the attribute name to value.|
|__delattr__(self, name)||Called when del self.name is called, should delete the attribute name.|
The _str_() and repr_() methods should be set up to return suitable string representations of the given object. In the case of_str_() this can be a basic string:
def _str_(self): return "%s: %g" % (self.name, self.balance)
The __repr__() method should return a value that will recreate the object when parsed by evalo. This means returning a string that represents the call necessary to recreate the object, for example, with the Account class you’d have to return a string that generated
Account (name, balance)
The real method definition would look something like this:
def_repr_(self): return "Account('%s',%g)"% (self.name, self.balance)
Emulating Sequence or Dictionary Objects
If you have created an object type that provides access to information through a sequence or dictionary interface, you must define the methods listed in Table 6-2. These are the methods called when you used the lent) function or access slices or elements directly from the objects.
Overloading Mathematical Operators
All user-defined objects can be made to work with all of Python’s built-in operators by adding implementations of the special methods that Python actually calls when an operator is used on any object, even the built-in types. This process is called operator overloading because you are effectively overloading an operator’s abilities to enable them to operate on a different object type.
Be aware however that overloading operators should be done where it makes sense to support the operator in context. For example, overloading with the _add_() method to create a new type of bank account based on two older accounts makes sense. Creating an HTTP class and overloading with the _add_() method probably doesn’t make sense, no matter how “cool” it might be to use the operator to perform the operation.
Overloading an operator to handle your own classes and objects is straightforward; all you need to do is define a specific method to handle the operation. For example, the_add_() method defines what happens when two objects of the same type are added together using the operator. For example, you can merge two of the bank accounts defined in the Account class:
def__add__ (self, other): return Account (self.name + 'and' + other.name, self.balance + other.balance)
|__len__(self)||Should return the length of self, called by the build-in len() function.|
|_getitem_(self, key)||Should return self[key].|
|_setitem_(self, key, value)||Should set the value of self[key] to value.|
|_delitem_(self, key)||Should delete self[key].|
|_delitem_(self,i,j)||Should return self[i:j ].|
|_getslice_(self,i j, value)||Should set self[i:j] to value.|
|_delslice_(self, i, j)||Should delete self[i:j].|
In this ase, _add_() returns the balance of the two account, as in this example:
bank = Account ('HSBC', -2000) creditcard = Account ('MBNA', -1000) assets = bank + creditcard
Now assets contains a new object whose name attribute is “HSBC and MBNA” and whose balance is 1000.
Although these are technically listed as mathematical operators, they apply to all objects where you would expect to use operators to manipulate them. For example, strings and other sequences define the _add_() and _mul_() methods. Table 6-3 list all the methods that you need to define when emulating numeric objects.
|_add_(self, other)||self + other|
|_sub_(self, other)||self – other|
|_mul_(self, other)||self * other|
|_div_(self, other)||self / other|
|_mod_(self, other)||self % other|
|_divmod_(self, other)||divmod (self, other)|
|_pow_(self, other [, modulo])||self**other, pow(self, other, modulo)|
|_1shift_(self, other)||self << other|
|_rshift_(self, other)||self >> other|
|_and_(self, other)||self & other|
|_or_(self, other)||self | other|
|_xor_(self, other)||self ^other|
|_radd_(self, other)||other + self|
|_rsub_(self, other)||other – self|
|_rmul_(self, other)||other * self|
|_rdiv_(self, other)||other / self|
|_rmod_(self, other)||other % self|
|_rdivmod_(self, other)||divmod(other, self)|
|_rpow_(self, other [,modulo])||other**self, pow(other, self, modulo)|
|_rlshift_(self, other)||other<< self|
|_rrshift_(self, other)||other >> self|
|_rand_(self, other)||other & self|
|_ror_(self, other)||other | self|
|_rxor_(self, other)||other ^ self|
|_invert_(self)||!self or ~self|
|_coerce_(self, other)||coerce(self, other)|
Any other functions that you define within a given class become the additional methods for your class. The format for each method is:
def METHOD (self [, args])
As mentioned earlier, the self argument is passed to every class method as it provides the connection to the object itself so that you can use and/or modify the attributes of a given object. In the earlier example, the deposit() and withdraw() methods are the other methods to the Account class.
There is limit to the number of methods that you can create within a class or to what they can do. methods are invoked by calling the method by name. For example, you can create a new instance of the Account variable in python using
bank = Account ('HSBC', 2000)
You can deposit money to the account using
Note that in the preceding case, the deposit() method doesn’t return any value because it’s modifying the balance attribute of the object directly. If you want to return a value, for example, to get the balance of the account, use the return statement as you would in other functions to return a value to the caller:
def accbalance (self): return self.balance
However, be aware that using a method for this operation is just duplicating the process of accessing the attribute directly. Given the accbalance() method above, the following two statements have identical results:
print "Balance:,bank.accbalance() print "Balance", bank.balance
Unless you are reformatting the output (as you do with the_str_() and repr_() methods), duplicating the effects of accessing an object’s attributes is largely pointless.
Making Objects Callable
You can make an object callable as if it operates just like a function. To do this, you need to define the call 0 method. This means that the statement
object(argi, arg2, ...) actually calls object.__call___(self, argl, arg2, ...)
This can be useful if you want a quick way to perform a specific operation on an object, the Account class, for example, might specify that calling an instance of the class automatically returns its balance.
Other uses of callable objects include instances where there is only one method defined within the class. For example, you may create a recipe object that when called. regurgitates a formatted version of itself suitable for display on the Web.
The parentheses following a class name are used by Python to identify the classes from which you inherit attributes and methods. As an extension to the example in the introduction to this chapter, you could define the two classes CreditCard and BankAccount, each of which inherits artifacts from the base class Account, like this:
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.acono = accno self.expiry = expiry self.limit = limit self.rate = rate. def withdraw( self, amount ): if abs( self.balance - amount) <self.limit: seltf.balance -= amount else: raise ValueError, "You're past your limit!" def add interest (self): self.balance -= ((abs(self.balance)*(self.rate/100)1/12)
Note that the withdraw() method within the CreditCard 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 Credit Card instance, notice that you can use the withdraw() and deposit() methods:
>>>visa = Credit Card( 'HSBC', -1000, '12345678', '02/99', 8000, 18) >>> visa.deposit(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 BankAccount and Credit Card classes:
Account._init__(self, name, balance)
This directly calls the constructor for the base class using the name and balance supplied to the class constructors 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.