Dark theme

Key ideas in depth: Variables in classes and modules; Polymorphism


Variables in classes and modules

It is worth going into a bit more depth on what kinds of variables we get from classes when class and instance variables do or don't exist.

Say we do this:

obj = module.ClassName()
print(obj.variable)

If an instance variable exists with this name, we get this.
If not, and a class attribute exists, we get that.
We can also get the latter with:

print(module.ClassName.variable)

If we adjust the instance variables of one object, it doesn't change the instance variables of anything of the same class.

For immutable variables, or variables replaced in total, if we adjust the class attributes of a class using:

ClassName.variable = "boo"

It changes everywhere. If we use:

object_name.variable = "boo"
or
self.variable = "boo"

and there isn't an instance variable of that name, but there is a class attribute, an instance variable is created for us (obviously, in the latter case). But, if the variable is mutable, and we do this:

object_name.variable[0] = "boo"
or
self.variable[0] = "boo"

and there isn't an instance variable of that name, but there is a class attribute, the class variable is changed, meaning it is change for everything.

One problem with this is that if a class we're using has its own class attribute "snake". And we do:

object.snake = 10

Thinking there's no variable "snake" in the class (why would there be in a language called "Python"), suddenly we've created a new variable that takes precedence over the old one. Worse still if snake is a method; this will also be replaced. You need to be very careful before assigning new variables to classes. In general, it is also better not to reuse variable names between scope levels. For more information on this, see the documentation on classes and variables.


Polymorphism

In general, method polymorphism is completed in most languages by allowing multiple methods with the same name, and then allowing the runtime to decide which to run based on the type of data sent to it. Python, however, doesn't generally allow the methods with the same name and different input arguments.

This comes to the fore with constructors. It's quite usual to have polymorphic constructors so that you can set up objects using different starting conditions. Default values for parameters go some way to helping with this, but the documentation suggests the following potential solution.

class C():
    def __init__(self, param=None):
        if param is None:
            # Setup one set of parameters.
        else:
            # Setup another set of parameters.

You can also use the * and ** operators to capture multiple arguments.


Printing objects

As we noted in the lecture, it is standard to override the __str__() function in classes so they print something nice when cast to a string. It is worth saying that there's another method that returns a string representation of an object: __repr__():

repr(object)

This is supposed (though results on this are mixed) to return a string which when evaluated will create the object. You may be wondering how you do this. There is infact a builtin function called eval which will run strings as code. For example:

a = [1,2,3]
b =(repr(a))
c = eval(b)
print(c)

HOWEVER: in general, running arbitrary strings you read from a file or are sent from outside the code is a security risk (indeed, even running code from a variable can, in some circumstances, be dangerous), so avoid using eval unless you absolutely know no one can inject code into your script using it.