Attributes and methods

One of the main reasons for working with classes is that objects can be grouped together and bound to a common object. We saw this already when looking at rational numbers; denominator and numerator are two objects which we bound to an instance of the RationalNumber class. They are called attributes of the instance. The fact that an object is an attribute of a class instance becomes apparent from the way they are referenced, which we have used tacitly before:

<object>.attribute

Here are some examples of instantiation and attribute reference:

q = RationalNumber(3, 5) # instantiation
q.numerator     # attribute access
q.denominator

a = array([1, 2])    # instantiation
a.shape

z = 5 + 4j    # instantiation
z.imag

Once an instance is defined we can set, change or delete attributes of that particular instance. The syntax is the same as for regular variables:

q = RationalNumber(3, 5) 
r = RationalNumber(7, 3)
q.numerator = 17
del r.denominator

Changing or deleting an attribute may have undesired side effects, which might even render the object useless. We will be learning more on this in the section Attributes that depend on each other. As functions are objects too, we can also use functions as attributes; they are called methods of the instance:

<object>.method(<arguments...>)

For example, let us add a method to the class RationalNumber that converts the number to a float:

class RationalNumber:
...
    def convert2float(self):
        return float(self.numerator) / float(self.denominator)

Again, this method takes as its first (and only) argument, self, the reference to the object itself. We use this method with a regular function call:

q = RationalNumber(10, 20)    # Defines a new object
q.convert2float() # returns 0.5   

This is equivalent to the following call:

RationalNumber.convert2float(q)

Note again that the object instance is inserted as the first argument of the function. This use of the first argument explains the error message that would occur if this particular method were used with additional arguments:

The q.convert2float(15) call provokes this error message:

TypeError: convert2float() takes exactly 1 argument (2 given)

The reason this does not work is that q.convert2float(15) is precisely equivalent to RationalNumber.convert2float(q,15), which fails because RationalNumber.convert2float takes only one argument.

Special methods

The special method __repr__ gives us the ability to define the way the object is represented in a Python interpreter. For rational numbers, a possible definition of this method could be as follows:

class RationalNumber:
 ...
    def __repr__(self):
        return '{} / {}'.format(self.numerator,self.denominator)

With this method defined, just typing q returns 10 / 20.

We would like to have a method that performs addition of two rational numbers. A first attempt could result in a method like this:

class RationalNumber:
...
    def add(self, other):
        p1, q1 = self.numerator, self.denominator
        if isinstance(other, int):
            p2, q2 = other, 1
        else:
            p2, q2 = other.numerator, other.denominator
        return RationalNumber(p1 * q2 + p2 * q1, q1 * q2)

A call to this method takes the following form:

q = RationalNumber(1, 2)
p = RationalNumber(1, 3)
q.add(p)   # returns the RationalNumber for 5/6

It would be much nicer if we could write q + p instead. But so far, the plus sign is not defined for the RationalNumber type. This is done by using the __add__ special method. So, just renaming add to __add__ allows for using the plus sign for rational numbers:

q = RationalNumber(1, 2)
p = RationalNumber(1, 3)
q + p # RationalNumber(5, 6)

The expression q + p is in fact an alias for the expression q.__add__(p). In the table (Table 8.1), you will find the special methods for binary operators, such as +-, or *.

Operator

Method

Operator

Method

+

__add__

+=

__iadd__

*

__mul__

*=

__imul__

-

__sub__

-=

__isub__

/

__truediv__

/=

__itruediv__

//

__floordiv__

//=

__ifloordiv__

**

__pow__

==

__eq__

!=

__ne__

<=

__le__

<

__lt__

>=

__ge__

>

__gt__

()

__call__

[]

__getitem__

Table 8.1: Some Python operators & corresponding class methods, you can find the complete list [31]

The implementation of those operators for a new class is called operator overloading. Another example of operator overloading is a method for examining whether two rational numbers are the same:

class RationalNumber:
...
    def __eq__(self, other):
        return self.denominator * other.numerator == 
            self.numerator * other.denominator

It is used like this:

p = RationalNumber(1, 2) # instantiation
q = RationalNumber(2, 4) # instantiation
p == q # True

Operations between objects belonging to different classes need special care:

p = RationalNumber(1, 2) # instantiation
p + 5  # corresponds to p.__add__(5)  
5 + p  # returns an error

By default, the + operator invokes the left operand’s method, __add__. We programmed it so that it allows both, objects of type int and objects of type RationalNumber. In the statement 5 + p, the operands are commuted and the __add__ method of the build-in int type is invoked. This method returns an error as it does not know how to handle rational numbers. This case can be handled by the method __radd__, with which we will equip the RationalNumber class now. The method __radd__ is called reverse addition.

Reverse operations

If operations like + are applied to two operands of different types, the corresponding method (in this case, __add__) of the left operand is invoked first. If this raises an exception, the reverse method (here, __radd__) of the right operand is called. If this method does not exist, a TypeError exception is raised.

Consider an example of reverse operation. In order to enable the operation 5+p where p is an instance of RationalNumber, we define this:

class RationalNumber:
   ....
    def __radd__(self, other):
        return self + other

Note that __radd__ interchanges the order of the arguments; self is the object of type RationalNumber while other is the object that has to be converted.

Using a class instance together with brackets, ( , ) or [ , ] invokes a call to one of the special methods __call__ or __getitem__, giving the instance the behavior of a function or of an iterable (refer to the Table 8.1 for these and other special methods):

class Polynomial:
...
    def __call__(self, x):
        return self.eval(x)

Which now may be used as follows:

p = Polynomial(...)
p(3.) # value of p at 3.

The __getitem__ special method makes sense if the class provides an iterator (It is recommended to refer section Iterators in Chapter 9, Iterating before you consider the following example).

The recursion ui+1 = a1ui+ a0ui-1is called a three-term recursion. It plays an important role in applied mathematics, in particular in the construction of orthogonal polynomials. We can set up a three-term recursion as a class in the following way:

import itertools

class  Recursion3Term:
    def __init__(self, a0, a1, u0, u1):
        self.coeff = [a1, a0]
        self.initial = [u1, u0]
    def __iter__(self):
        u1, u0 = self.initial
        yield u0  # (see also Iterators section in Chapter 9) 
        yield u1
        a1, a0 = self.coeff
        while True :
            u1, u0 = a1 * u1 + a0 * u0, u1
            yield u1
    def __getitem__(self, k):
        return list(itertools.islice(self, k, k + 1))[0]

Here, the __iter__ method defines a generator object, which allows us to use an instance of the class as an iterator:

r3 = Recursion3Term(-0.35, 1.2, 1, 1)
for i, r in enumerate(r3):
    if i == 7:
        print(r)  # returns 0.194167
        break

The __getitem__ method enables us to directly access the iterates as if r3 were a list:

r3[7] # returns 0.194167

Note that we used itertools.islice when coding the __getitem__ method (refer to section Iterators of Chapter 9, Iterating, for more information). An example of the use of __getitem__ together with slices and the function ogrid is given in the section Function with two variables in Chapter 5, Advance Array Concepts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.141.219