We have already seen how we can use a dictionary to group related data together, and how we can use functions to create shortcuts for commonly used groups of statements. A function performs an action using some set of input parameters. Not all functions are applicable to all kinds of data. Classes are a way of grouping together related data and functions which act upon that data. Show
A class is a kind of data type, just like a string, integer or list. When we create an object of that data type, we call it an instance of a class. As we have already mentioned, in some other languages some entities are objects and some are not. In Python, everything is an object – everything is an instance of some class. In earlier versions of Python a distinction was made between
built-in types and user-defined classes, but these are now completely indistinguishable. Classes and types are themselves objects, and they are of type The data values which we store inside an object are called attributes, and the functions which are associated with the object are called methods. We have already used the methods of some built-in objects, like strings and lists. When we design our own objects, we have to decide how we are going to group things together, and what our objects are going to represent. Sometimes we write objects which map very intuitively onto things in the real world. For example, if we are writing code to simulate chemical reactions, we might have Sometimes we may create objects which don’t have any kind of real-world equivalent, just because it’s useful to group certain functions together. Defining and using a class¶Here is an example of a simple custom class which stores information about a person: import datetime # we will use this for date objects class Person: def __init__(self, name, surname, birthdate, address, telephone, email): self.name = name self.surname = surname self.birthdate = birthdate self.address = address self.telephone = telephone self.email = email def age(self): today = datetime.date.today() age = today.year - self.birthdate.year if today < datetime.date(today.year, self.birthdate.month, self.birthdate.day): age -= 1 return age person = Person( "Jane", "Doe", datetime.date(1992, 3, 12), # year, month, day "No. 12 Short Street, Greenville", "555 456 0987", "" ) print(person.name) print(person.email) print(person.age()) We start the class definition with the Inside the class body, we define two functions – these are our object’s methods. The first is called The second method is a custom method which calculates the age of our person using the birthdate and the current date. Note
You may have noticed that both of these method definitions have In some languages this parameter is implicit – that is, it is not visible in the function signature – and we access it with a special keyword. In Python it is explicitly exposed. It doesn’t have to be called Now you should be able to see that our The Note that the Remember that defining a function doesn’t make the function run. Defining a class also doesn’t make anything run – it just tells Python about the class. The class will not be defined until Python has executed the entirety of the definition, so you can be sure that you can reference any method from any other method on the same class, or even reference the class inside a method of the class. By the time you call that method, the entire class will definitely be defined. Exercise 1¶
Instance attributes¶It
is important to note that the attributes set on the object in the In some languages you must provide a list of the object’s attributes in the class definition, placeholders are created for these allowed attributes when the object is created, and you may not add new attributes to the object later. In Python, you can add new attributes, and even new methods, to an object on the
fly. In fact, there is nothing special about the def age(self): if hasattr(self, "_age"): return self._age today = datetime.date.today() age = today.year - self.birthdate.year if today < datetime.date(today.year, self.birthdate.month, self.birthdate.day): age -= 1 self._age = age return age Note Starting an attribute or method name with an underscore ( We could even add a completely unrelated attribute from outside the object: person.pets = ['cat', 'cat', 'dog'] It is very common for an object’s methods to update the values of the object’s attributes, but it is considered bad practice to create new attributes in a method without initialising them in the The In the Initialising all our attributes in An getattr, setattr and hasattr¶What if we want to get or set the value of an attribute of an object without hard-coding its name? We may sometimes want to loop over several attribute names and perform the same operation on all of them, as we do in this example which uses a dictionary: for key in ["a", "b", "c"]: print(mydict[key]) How can we do something similar with an object? We can’t use the for key in ["a", "b", "c"]: print(getattr(myobject, key, None)) Note that Similarly, for key in ["a", "b", "c"]: setattr(myobject, key, mydict[key]) The first parameter of As we saw in the previous There’s nothing preventing us from using getattr(myobject, "a") # means the same thing as myobject.a You should only use these functions if you have a good reason to do so. Exercise 2¶
Class attributes¶All the attributes which are defined on a We define class attributes in the body of a class, at the same indentation level as method definitions (one level up from the insides of methods): class Person: TITLES = ('Dr', 'Mr', 'Mrs', 'Ms') def __init__(self, title, name, surname): if title not in self.TITLES: raise ValueError("%s is not a valid title." % title) self.title = title self.name = name self.surname = surname As you can see, we access the class attribute All the Class attributes are often used to define constants which are closely associated with a particular class. Although we can use class attributes from class instances, we can also use them from class objects, without creating an instance: # we can access a class attribute from an instance person.TITLES # but we can also access it from the class Person.TITLES Note that the class object doesn’t have access to any instance attributes – those are only created when an instance is created! # This will give us an error Person.name Person.surname Class attributes can also sometimes be used to provide default attribute values: class Person: deceased = False def mark_as_deceased(self): self.deceased = True When we set an attribute on an instance which has the same name as a class attribute, we are overriding the class attribute with an instance attribute, which will take precedence over it. If we create two class Person: pets = [] def add_pet(self, pet): self.pets.append(pet) jane = Person() bob = Person() jane.add_pet("cat") print(jane.pets) print(bob.pets) # oops! What we should do in cases like this is initialise the mutable attribute as an instance attribute, inside class Person: def __init__(self): self.pets = [] def add_pet(self, pet): self.pets.append(pet) jane = Person() bob = Person() jane.add_pet("cat") print(jane.pets) print(bob.pets) Note that method definitions are in the same scope as class
attribute definitions, so we can use class attribute names as variables in method definitions (without class Person: TITLES = ('Dr', 'Mr', 'Mrs', 'Ms') def __init__(self, title, name, surname, allowed_titles=TITLES): if title not in allowed_titles: raise ValueError("%s is not a valid title." % title) self.title = title self.name = name self.surname = surname Can we have class methods? Yes, we can. In the next section we will see how to define them using a decorator. Exercise 3¶
Class decorators¶In the previous chapter we learned about decorators – functions which are used to modify the behaviour of other functions. There are some built-in decorators which are often used in class definitions. @classmethod¶Just like we can define class attributes, which are shared between all instances of a class, we can define class methods. We do this by using the A class method still
has its calling object as the first parameter, but by convention we rename this parameter from What are class methods good for? Sometimes there are tasks associated with a class which we can perform using constants and other class attributes, without needing to create any class instances. If we had to use instance methods for these tasks, we would need to create an instance for no reason, which would be wasteful. Sometimes we write classes purely to group related constants together with functions which act on them – we may never instantiate these classes at all. Sometimes it is useful to write a class method which creates an instance of the class after processing the input so that it is in the right format to be passed to the class constructor. This allows the constructor to be straightforward and not have to implement any complicated parsing or clean-up code: class Person: def __init__(self, name, surname, birthdate, address, telephone, email): self.name = name # (...) @classmethod def from_text_file(cls, filename): # extract all the parameters from the text file return cls(*params) # this is the same as calling Person(*params) @staticmethod¶A static method doesn’t have the calling object passed into it as the first parameter. This means that it doesn’t have access to the rest of the class or instance at all. We can call them from an instance or a class object, but they are most commonly called from class objects, like class methods. If we are using a class to group together related methods which don’t need to access each other or any other data on the class, we may want to use this technique. The advantage of using static methods is that we eliminate unnecessary Here is a brief example comparing the three method types: class Person: TITLES = ('Dr', 'Mr', 'Mrs', 'Ms') def __init__(self, name, surname): self.name = name self.surname = surname def fullname(self): # instance method # instance object accessible through self return "%s %s" % (self.name, self.surname) @classmethod def allowed_titles_starting_with(cls, startswith): # class method # class or instance object accessible through cls return [t for t in cls.TITLES if t.startswith(startswith)] @staticmethod def allowed_titles_ending_with(endswith): # static method # no parameter for class or instance object # we have to use Person directly return [t for t in Person.TITLES if t.endswith(endswith)] jane = Person("Jane", "Smith") print(jane.fullname()) print(jane.allowed_titles_starting_with("M")) print(Person.allowed_titles_starting_with("M")) print(jane.allowed_titles_ending_with("s")) print(Person.allowed_titles_ending_with("s")) @property¶Sometimes we use a method to generate a property of an object dynamically, calculating it from the object’s other properties. Sometimes you can simply use a method to access a single attribute and return it. You can also use a different method to update the value of the attribute instead of accessing it directly. Methods like this are called getters and setters, because they “get” and “set” the values of attributes, respectively. In some languages you are encouraged to use getters and setters for all attributes, and never to access their values directly – and there are language features which can make attributes inaccessible except through setters and getters. In Python, accessing simple attributes directly is perfectly acceptable, and writing getters and setters for all of them is considered unnecessarily verbose. Setters can be inconvenient because they don’t allow use of compound assignment operators: class Person: def __init__(self, height): self.height = height def get_height(self): return self.height def set_height(self, height): self.height = height jane = Person(153) # Jane is 153cm tall jane.height += 1 # Jane grows by a centimetre jane.set_height(jane.height + 1) # Jane grows again As we can see, incrementing the height attribute through a setter is much more verbose. Of course we could write a second setter which increments the attribute by the given parameter – but we would have to do something similar for every attribute and every kind of modification that we want to perform. We would have a similar issue with in-place modifications, like adding values to lists. Something which is often considered an advantage of setters and getters is that we can change the way that an attribute is generated inside the object without affecting any code which uses the object. For example, suppose that we initially created a But what if our code accesses the class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def fullname(self): return "%s %s" % (self.name, self.surname) jane = Person("Jane", "Smith") print(jane.fullname) # no brackets! There are also decorators which we can use to define a setter and a deleter for our attribute (a deleter will delete the attribute from our object). The getter, setter and deleter methods must all have the same name: class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def fullname(self): return "%s %s" % (self.name, self.surname) @fullname.setter def fullname(self, value): # this is much more complicated in real life name, surname = value.split(" ", 1) self.name = name self.surname = surname @fullname.deleter def fullname(self): del self.name del self.surname jane = Person("Jane", "Smith") print(jane.fullname) jane.fullname = "Jane Doe" print(jane.fullname) print(jane.name) print(jane.surname) Exercise 4¶
Inspecting an object¶We can check what properties are defined on an object using the class Person: def __init__(self, name, surname): self.name = name self.surname = surname def fullname(self): return "%s %s" % (self.name, self.surname) jane = Person("Jane", "Smith") print(dir(jane)) Now we can see our attributes and our method – but what’s all that other stuff? We will discuss inheritance in the next chapter, but for now all you need to know is that any class that you define has Note in Python 2 we have to inherit from This is why you can just leave out the Many default methods and attributes that are found in built-in Python objects have names which begin and end in double underscores, like We can use Here are some examples of special object properties:
Exercise 5¶
Overriding magic methods¶We have already seen how to overload the import datetime class Person: def __init__(self, name, surname, birthdate, address, telephone, email): self.name = name self.surname = surname self.birthdate = birthdate self.address = address self.telephone = telephone self.email = email def __str__(self): return "%s %s, born %s\nAddress: %s\nTelephone: %s\nEmail:%s" % (self.name, self.surname, self.birthdate, self.address, self.telephone, self.email) jane = Person( "Jane", "Doe", datetime.date(1992, 3, 12), # year, month, day "No. 12 Short Street, Greenville", "555 456 0987", "" ) print(jane) Note that when we insert the birthdate object into the output string with It is also often useful to overload the comparison methods, so that we can use comparison operators on our person objects. By default, our person objects are only equal if they are the same object, and you can’t test whether one person object is greater than another because person objects have no default order. Suppose that we want our person objects to be equal if all their attributes have the same values, and we want to be able to order them alphabetically by surname and then by first name. All of the magic comparison methods are independent of each other,
so we will need to overload all of them if we want all of them to work – but fortunately once we have defined equality and one of the basic order methods the rest are easy to do. Each of these methods takes two parameters – class Person: def __init__(self, name, surname): self.name = name self.surname = surname def __eq__(self, other): # does self == other? return self.name == other.name and self.surname == other.surname def __gt__(self, other): # is self > other? if self.surname == other.surname: return self.name > other.name return self.surname > other.surname # now we can define all the other methods in terms of the first two def __ne__(self, other): # does self != other? return not self == other # this calls self.__eq__(other) def __le__(self, other): # is self <= other? return not self > other # this calls self.__gt__(other) def __lt__(self, other): # is self < other? return not (self > other or self == other) def __ge__(self, other): # is self >= other? return not self < other Note that Sometimes it makes sense to exit with an error if the other object is not of the same type as our object, but sometimes we can compare two compatible objects even if they are not of the same type. For example, it makes sense to compare Note Python 2 also has a Exercise 6¶
Answers to exercises¶Answer to exercise 1¶
Answer to exercise 2¶
Answer to exercise 3¶
Answer to exercise 4¶
Answer to exercise 5¶
Answer to exercise 6¶
Which of the following function is called when a class is used to create a new object in Python?Note: The __init__() function is called automatically every time the class is being used to create a new object.
What is the function that gets called when a class is created?The constructor method is a special method of a class for creating and initializing an object instance of that class.
Which method is called automatically when you use a class to create an object?If a class does not explicitly declare any, the Java compiler automatically provides a no-argument constructor, called the default constructor. This default constructor calls the class parent's no-argument constructor, or the Object constructor if the class has no other parent.
What is used to create a new instance of a class?The correct answer is: (b) New. In Java, the ''new'' keyword is used to create an object, i.e., an instance of a class.
|