• No results found

Class and object attributes

Object classes serve to bring together information and class functions that act on that information. To hold this information, Python classes have simple attributes; plain variables tied to the object. A very simple example would be to associate a variable called description to the Molecule class so that for a given instance called molecule, we can do the following to get its value:

print(molecule.description)

There are two ways in which such variables can be associated with a class. They can be an object attribute and belong to a specific object, or a class attribute and belong to the class as a whole. Class attributes are defined outside all function blocks and will have the same value for all objects. Object attributes are defined inside class functions and must be set explicitly for each object.

For example, the AminoAcid class might have a dictionary containing the molecular masses of amino acids, with the key being the one-letter code of the amino acid.4 The weight of amino acids is independent of which particular protein molecule we may be considering,5 so in this case we could define a dictionary as a class attribute.

class AminoAcid:

massDict = {'A': 71.07, 'R': 156.18, … }

Such class attributes can be accessed either via an object instance or via the class name. As with functions, attribute access is via the ‘dot’ syntax, but note that there are no parentheses when you access an attribute; that is how you can tell the difference between a function call and an attribute access. Here, if you have an object lysine made from the AminoAcid class, then you can get to the dictionary attribute via:

massDict = lysine.massDict

However, because it is a class attribute you could also access it via the name of the class:

massDict = AminoAcid.massDict

Class attributes are often used for variables which do not change, because they are defined for the class as a whole and their values are made available to all instances of that kind of object. The AminoAcid class might have another class attribute called acceptableCodes, which lists the codes that are deemed to be valid for an aminoAcid. In fact, here these could just be the keys from the massDict dictionary, so we could do: class AminoAcid:

massDict = {'A': 71.07, 'R': 156.18, … } acceptableCodes = set(massDict.keys())

In Python 2, the above massDict.keys() call gives back a list of keys for mostly historic reasons, because lists and dictionaries were introduced into Python long before sets. Although we could leave it as a list, we turn it into a set for efficiency reasons; determining whether something is in a set is faster than determining whether it is in a list. In Python 3 massDict.keys() returns a view onto the keys, and the set() converts this explicitly into a set.

To access the massDict class variable inside a class function we write ‘self.massDict’. But it is notable that outside a class function, in the context of the main code block for a class, there is no self so above we would not write ‘self.massDict.keys()’. Also, when the Python interpreter sees this usage of massDict it is not yet finished with reading the class definition, so technically the class does not yet exist. Thus, the syntax ‘AminoAcid.massDict.keys()’ is also not allowed inside the class code block. Instead, a class attribute that refers to a previous class attribute just uses the name directly, as illustrated here.

Interestingly, bare function names, without the parentheses, are also class attributes, so you can get hold of them and then call them later. For example, with the function getSequence in the Protein class you could do: getSequenceFunc = Protein.getSequence getSequenceFunc(protein) # same as protein.getSequence() The AminoAcid class might have an attribute for the one-letter code of the amino acid. The code letter depends on the particular amino acid in question, and not on the class as a whole. Thus, this provides an example where it is more natural to have an object attribute, rather than a class attribute. Accordingly, we might have a function setCode() where this attribute, which we imaginatively call code, can be set (based upon the input value) by using the ‘dot’ syntax to tie the variable to self: class AminoAcid: def setCode(self, code): self.code = code Then for the object lysine, built from this class, you can get at its code, once it has been set: lysine = AminoAcid() lysine.setCode('Lys') code = lysine.code It is relatively easy to see the particular object instance called lysine becomes the self in the function definition. Taken together, you might have the following implementation of the getSequence() function to fetch residue codes from a list of AminoAcid objects:

def getSequence(self):

return [aminoAcid.code for aminoAcid in self.aminoAcids]

This assumes that the code has indeed been set for all the amino acids making up the protein; and that the attribute self.aminoAcids is defined somehow for the Protein class. As it happens, in the situation described here we would probably not introduce the function setCode() at all. Instead, we would set the important code attribute when we create the object in what is called the constructor, a special function called whenever a new object is made, as discussed in the next section. Nonetheless other, less fundamental attributes might use setter functions.

Lastly we must come clean and admit that Python actually has another way to set attributes, namely:

aminoAcid.code = 'A'

This syntax allows you to set any attribute to any value, without having to write setter functions, or even having to define the attribute elsewhere. Because it is so simple, this syntax is very popular among Python programmers. However, we have chosen to introduce the more formal way first, because classes depend on knowing which attributes are present, what the values of the attributes are and possibly how those values relate. For example, in setCode() we can easily introduce a check to ensure that an amino acid code is valid. When designing a new class, setting all kinds of attributes in an unregulated way is not the best way to start. However, as we will go on to discuss in the Properties section at the end of Chapter 8, there is actually a way to get the best of both worlds so that you can use the above assignment syntax while under-the-hood it will really be using a special setter function.