Class constructors
All objects have a life cycle; at the beginning you have to create a new object, and at the
end the object is removed. The creation of an object is handled in a special function that is
called a constructor, and its removal is handled in a function that is called a destructor.
Python has automatic garbage collection, which means that objects are (eventually)
deleted from memory when they are no longer accessible to the program, so normally you
do not have to define a destructor. Even when you do want to clean up deliberately, when
you are done with an object, it is normal to define a separate function and call it explicitly,
rather than use a destructor, since the latter is only called when Python gets around to
doing its garbage collection.
A constructor function, when present, is always called whenever the corresponding
object is created. The constructor’s definition always has the special name __init__;
double underscore followed by ‘init’ followed by double underscore.
6
As with all Python
class functions, the first argument is the object handle, i.e. ‘self’. After the self you can
have any number of other arguments that might be useful to set the object up, or maybe no
arguments at all. Many classes have a key, an attribute that uniquely identifies objects of a
given class. Usually this key is either passed into the constructor function as one of the
arguments or it is deduced from the arguments that are passed in. For example, suppose
you decided that for a Molecule there is a name that identifies it. We could then do:
class Molecule:
def __init__(self, name):
# contents of constructor function
Unlike a normal class function, which you would call using the same name as it was
defined with, here you do not utter the constructor name ‘__init__’ at all to call it. Instead,
the class name is used directly with round parentheses. Thus, to create a Molecule you
would do:
molecule = Molecule(name)
The next question is what to do in the constructor with the information that is passed in.
Naturally, that is entirely up to the person writing the code, according to what the
requirements of the class are. A very common practice is to keep a reference to the
arguments that are passed to the constructor, so that they can be referred to later via the
object:
class Molecule:
def __init__(self, name):
self.name = name
What this syntax means is that any object of the Molecule class now has an attribute
that is called name; the variable is bound to the self that represents the particular instance
of an object. You can thus do something like:
molecule = Molecule(name) # Make new object
print('molecule name = %s' % molecule.name) # Use the object
Note that the attribute self.name is created on-the-fly inside the constructor. You do not
have to otherwise specify that you intend to create this, or any other, attribute in Python.
Here the value is set directly from an argument that is passed into the constructor when the
new object is made, but it is possible that the value is somehow determined indirectly.
As with any function, often you should check that the value passed into an object
constructor makes sense. So here, for Molecule, perhaps we want the name to be set and
not be the empty string or None. Accordingly, we could check that the name is defined
(semantically true) and if it is not we can trigger an error and go no further:
class Molecule:
def __init__(self, name):
if not name:
raise Exception('name must be set to something')
self.name = name
You can create attributes in any class function (or directly on the object), but it is
normal to create most of them in the constructor, either directly in the constructor function
or indirectly via another function that is itself called from within the constructor.
Sometimes an attribute might not yet have a known value at the moment when the object
is created, and in this case it is normal practice to set it to a default value, for example,
None, if there is no other means of determining it. The value can then be set later when the
information is available.
As another example, the AminoAcid constructor could be implemented as follows,
giving an informative error message about what should have been done:
class AminoAcid:
def __init__(self, code):
if code not in self.acceptableCodes:
text = 'code = "%s", must be in list %s'
raise Exception(text % (code, sorted(self.acceptableCodes)))
self.code = code
For a subclass, such as the above Protein, you might decide that the superclass
constructor is all that you need; remembering that a subclass inherits all of the functions
and attributes of its superclass. In this case the subclass would have no extra constructor
defined, and when such an object is created it will automatically use the superclass
constructor. To create a subclass object you still use its class name, even if it doesn’t
explicitly have its own version of the constructor function:
protein = Protein(name)
Typically, however, a subclass would define its own __init__ function and thus override
the superclass constructor, because there are usually more setup operations that need to be
done for this kind of object, compared to the class on which it is based.
It is very common that, if you do overwrite the constructor, you also want to call the
superclass constructor from inside the subclass constructor; the setup for the superclass is
also useful for the subclass and you want to implement both pieces of code. To illustrate
this, for the Protein class it’s possible we would pass in the sequence of one-letter codes,
which determine the amino acid components of the protein, as part of its constructor, and
so have:
class Protein(Molecule):
def __init__(self, name, sequence):
Molecule.__init__(self, name)
# now the Protein specific initialisation
Note the syntax: inside the class the constructor is called via __init__, not via the class
name; we are not making a new object yet, just calling the setup function. Also note that
Molecule.__init__ is the constructor for the Molecule superclass. We cannot use the more
normal syntax self.__init__ in this context because that refers to the Protein __init__
function, not the Molecule __init__ function.
Given that we are passing in the molecular sequence to the constructor, it would be
natural to create the AminoAcids objects there:
class Protein(Molecule):
def __init__(self, name, sequence):
Molecule.__init__(self, name)
self.aminoAcids = []
for code in sequence:
aminoAcid = AminoAcid(code)
self.aminoAcids.append(aminoAcid)
Note that the sequence could be a list of one-letter strings each of which represents one
code, or it could be a string with each position representing one one-letter code, it does not
matter which because you iterate over them (consider each element in turn) in the same
way in Python.
Do'stlaringiz bilan baham: |