Dark theme

Key Ideas


The key ideas for this part centre on making classes and objects.


Functions

An object is a variable with a coder-defined type. You create such a variable thus:

object_variable_name = ClassName()

The basic class declaration is:

class ClassName():
    # stuff

Objects can have variables ("instance variables") and functions inside them. You access these using the dot operator:

object_variable_name.variable_inside = 10
object_variable_name.function_inside()

You set up instance variables in objects by assigning them within a method in the class. In general this is the __init__ method. The variables also have to be assigned to self, which, in this case, will represent the object.

class ClassName():
    def __init__(self):
        self.variable_inside = 20

Functions within classes are set up like __init__. In general, you can think of functions within classes as fairly independent of either object or class (indeed, you can treat them as objects in their own right and assign labels to them as we saw earlier in the course). Because of this, they need something to tie them to the object they are associated with. This is done through the self variable:

class ClassName():
    def function_inside(self):
        self.variable_inside = 20
        self.some_other_function()

When a function is called inside an object, the function is found within the class, and wrapped up in other code and run. Part of the wrapping up process involves passing the object into the function called, where it is attached to the first variable in the function declaration. Traditionally this is given the identifier self. If you forget to put this in the function declaration and call the function with nothing, Python will warn you that your declaration takes one less variable than is being passed as the object is being passed invisibly in.

The passing in of the object allows us to talk about variables and other functions associated with the object, as above.

Because of this wrapping process, we usually call functions inside objects "methods" to distinguish them. We call a method called from an object a "bound" method, and a method called from a class a "unbound" method.

object_variable_name.function_inside() # Bound
ClassName.function_inside() # Unbound

In general most methods are actually unbound, the binding only coming from the object itself being pass into the class version of the method.


Because we have added more blocks to the code, there are now a variety of positions variables can first be assigned, and therefore a variety of scopes. The following details which types of variables are defined where, and how we'd refer to them:

global_variable = "gv"

class ClassName():
    class_attribute = "ca"

    def __init__(self):
        self.instance_variable = "iv"
        method_local_variable = "mv"
        global global_variable
        print(GeoPoint.class_attribute)
        print(self.instance_variable)
        print(method_local_variable)

    def another_method(self):
        # No access to method_local_variable here.

The main things to note are that object instance variables need to be defined as associated with self and that if an instance variable doesn't exist but you try to refer to it outside the object or class, and a class-level variable with the same name exists, you get the class-level variable. If it is mutable and you change the contents, it stays being the class level variable. If you change it, you get a new instance variable. This is generally confusing, so avoid messing with variables like this and define everything as instance variables.


We can invisibly inherit code from other classes by:

class SubClass(SuperClassToInherit):

From then on, you can use the functions and variables of the superclass as if they were in the subclass. You can also write over ("override") the superclass variables and functions by writing your own variables and functions with the same name in the subclass (superclass versions can be referred to with the super keyword: super.variable). Both these things mean that if, for example, a function requires a specific type of object, the subclass will do instead of the superclass.


In general, we try to avoid direct access to variables in objects. Object Oriented Philosophy dictates that it is better to get at objects through methods that can check that it is appropriate for you to adjust or read them. Such methods are generally called "set_variable_name" and "get_variable_name"; these are known as "mutator" and "accessor" methods respectively. In Python these are generally attached to calls to access variables directly, so such calls are redirected, using a Property setup in the class.

class C():
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")