Programmer's Python - Class
Written by Mike James   
Monday, 25 May 2020
Article Index
Programmer's Python - Class
Instance Attributes

Instance Attributes

We can think of the attributes that the class provides the instance as class attributes. Notice that all instances of the class share the same attributes as they are provided by the class object.

How can the instance acquire its own attributes?

The answer is that instance attributes are created when you assign to an instance.

In this case the usual rule in Python that assignment creates an attribute or a variable is followed.

This means that after assignment the instance has its own attribute.

For example:

myObject=myClass()
print(myClass.myAttribute)
myObject.myAttribute=3
myClass.myAttribute=2
print(myClass.myAttribute)
print(myObject.myAttribute)

In this case after assignment to myObject.myAttribute the instance no longer uses the class attribute and so you see 1,2, 3 printed. 

Assigning to the class attribute has no effect on the instance attribute and vice versa.

The rule is that changes to an instance’s dictionary never update the class dictionary. For example, you can delete an instance’s attribute and after this the class attribute of the same name will be used.

Also notice that you can add attributes to an instance that are not defined in the class.

That is, you are not restricted to simply overriding the class attributes. However, it is a good idea not to add attributes to an instance that are not defined in the class because this spoils the idea that the class is in some way a definition of all of the instances it creates. It is ad-hoc programming and likely to result in a very messy program.

These ideas are discussed more in Chapter 7.

What is a Method?

Attributes can be any object but when attributes are functions things are slightly different. A function that is an attribute of an object created using a class is converted into a method.

Before we look at how this works it is worth spending a few minutes looking at the general problem and exactly what a method is.

When we first started writing programs in higher-level languages, best practice was to write a function for whatever you needed to do.

For example, if you needed to sort an array, you would write a sort function which accepted a few parameters that determined the data and the operation:

sort(myArray,order)

where myArray is the data that you want to sort, and order is a parameter that sets the sort order to be used.

Later on we moved over to object-oriented programming where data and the functions that process the data are grouped together into entities called objects. In this case the functions like sort became methods of the data that they were to operate on.

So an Array object would have a sort method and you would write the sort operation as:

myArray.sort(order)

You can see that this is a small change from the use of myArray as a parameter to the sort function to myArray as an object and sort method. You could say that the whole of the shift from functions to object-oriented programming is all about the shift of a parameter from inside the function to outside.

Looking a little deeper the simplification that this shift brings about is well worth it.

The sort function can now use the data in myArray more or less by default and this makes it possible to create an isolation from the rest of the program. It also brings about a complete change in the way that we think about functions.

For example, you could say that myArray “knows how” to sort itself. Another object myList, say, may also “know how” to sort itself using its own sort function which isn’t the same as the Array sort function.

This means that each data structure can have its own sort function and we can avoid having to have an arraySort function and a listSort function and so on.

This is a limited form of polymorphism and it is one of the huge advantages of object-oriented programming. Yet it is strange that even the most enthusiastic object-oriented programmers don’t think that there is any problem in having overloaded functions to deal with different data types which is a problem much better solved by polymorphism. It is much better to have the data determine which function to use rather than the signature of a detached function.

So the key point is that:

sort(myArray,order)

is a function that accepts the data it is going to work with as its first parameter and:

myArray.sort(order)

is a method that belongs to the myArray object.

How Functions Become Methods

You can see that if you want to use a function as a method all you have to do is convert that call to the:

myArray.sort(*args)

method into a call to the:

myArray(sort,*args)

function.

In Python this transformation from method to function works in a very direct way. Within the class you create an attribute as a function attribute and the first parameter, usually called self, is the instance that the function is to be a method of.

For example:

class myClass:
    myAttribute=1
    def myFunc(self):
        print(self,myAttribute)

myFunc is just a standard function object. The first parameter is only named self by convention – you can use any name you like but be prepared to confuse everyone if you do.

If you call myFunc as an attribute of the myClass object then there is nothing new:

print(myClass.myFunc(myClass))

and it prints 1 as you would expect. Notice that you have to explicitly pass a value for self and this is just the object you want the function to operate on.

Things are different if we create an instance of the class and call myFunc:

myObject=myClass()
print(myObject.myFunc(myObject))

If you try this out, calling myFunc as if it was a function as before, you will find that you get an error message:

print(myObject.myFunc(myObject))
TypeError: myFunc() takes 1 positional argument but 2 were given

which, like most error messages, doesn’t actually tell you what the problem is.

More to the point when you look at it then you have to come to the conclusion that the error message lies! It is quite clear that myFunc takes one argument but “2 were given” is clearly wrong. Only one argument was given so what is going on?

The answer is that when you call a function defined on a class from an instance, the first thing that happens is that the attribute is looked up in the instance’s dictionary as usual; when it isn’t found it is accessed via the class object’s dictionary.

However, instead of returning the function object, the attribute access creates a method object as a wrapper for the function object which is something like:

def method(*args,**kwargs):    
     return myClass.myFunc(myObject,*args,**kwargs)

In other words, the function is converted to a method by an object which calls it with the first parameter set to the object that the function is now bound to. Now you can see why the call has two arguments – one is automatically provided by the system and the second, the one you supplied, is superfluous.

The correct way to call the instance’s method is:

myObject=myClass()
print(myObject.myFunc())

The system supplies the self parameter in the call – myObject in this case.

Notice that you don’t have to call the method to create it; accessing the attribute is enough to wrap the function object as a method object.

For example:

myMethod=myObject.myFunc

Now myMethod is a method object with a number of attributes that reflects its role as a wrapper that converts a function into a method:

  • __self__ is the instance, i.e. myObject in this case

  • __func__ is the unmodified function object, i.e. myClass.myFunc in this case

  • __doc__ is the docstring of the function, i.e. myMethod.__doc__= myClass.myFunc.__doc__

  • __name__ is the function's name, i.e. myMethod.__name__= myFunc.__name__

  • __module__ is the module that the function is in, i.e. myMethod.__module__ = myObject.myFunc.__module__ 

Of course, when you call myMethod:

myMethod(*args,**kwargs)

it calls:

myMethod.__func__(myMethod.__self__, *args, **kwargs)

and this is how functions become methods in Python.

You can think of this as in the illustration below:

methods

  • Bind Early, Bind Often
  • Class Binds
  • Other Bindings
  • The Initializer __init__
  • Multiple Constructors
  • Class for Methods, Instance for Data

Summary

  • Unlike class in other languages, in Python a class is an object.

  • A class object is a callable which means it can be called like a function.

  • When called as a function a class returns an instance.

  • At its simplest an instance has no attributes. Any attributes you try to access on the instance are resolved by attributes on the class object.

  • If you assign to an attribute then it is created as an instance attribute.

  • Function attributes defined on the class object are converted into method objects when accessed via an instance.

  • Methods call the function object using self, the instance, as the first parameter. What this means is that you write function attributes with an explicit self parameter but when you call a method you do not specify self.

  • Methods are bound to their instance when first accessed; this is a form of early binding.

  • In addition to instance methods you can also create static and class methods.

  • The initalizer __init__ can be used to add attributes to an instance and to initialize them.

  • You can create multiple constructors using class methods.

  • In Python it makes sense to place all methods in the class body and all data attributes in the __init__. This allows methods to be shared by all instances and data to be unique to each instance.

 

Programmer's Python
Everything is an Object

Is now available as a print book: Amazon

pythoncover


Contents

  1. Hello Python World
  2. Variables, Objects and Attributes
  3. The Function Object
      Extract - Function Objects
  4. Scope, Lifetime and Closure
      Extract - Local and Global
  5. Advanced Functions
      Extract -  Decorators 
  6. Class Methods and Constructors
  7. Inside Class
  8. Metaclass
  9. Advanced Attributes
      Extract - Properties
  10. Custom Attribute Access
      Extract -  Custom Attributes
      Extract -  Default Methods ***NEW
  11. Single Inheritance
  12. Multiple Inheritance
  13. Class and Type
      Extract - Class
  14. Type Annotation
      Extract - Type Annotation 
  15. More Magic - Operator Overloading

 

Advanced Attributes

Related Articles

Creating The Python UI With Tkinter

Creating The Python UI With Tkinter - The Canvas Widget

The Python Dictionary

Arrays in Python

Advanced Python Arrays - Introducing NumPy

square

 



 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on, Twitter, Facebook or Linkedin.

Banner

<ASIN:1871962587>



Last Updated ( Monday, 25 May 2020 )