<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Classes and Object Oriented Programming
We have looked at functions which take input and return output (or do things to the input). However, sometimes it is useful to think about objects first rather than the actions applied to them.
Think about a polynomial, such as the cubic
\begin{equation}
  p(x) = 12 - 14 x + 2 x^3.
\end{equation}
This is one of the standard forms that we would expect to see for a polynomial. We could imagine representing this in Python using a container containing the coefficients, such as
Step1:  The order of the polynomial is given by the number of coefficients (minus one), which is given by len(p_normal)-1.
However, there are many other ways it could be written, which are useful in different contexts. For example, we are often interested in the roots of the polynomial, so would want to express it in the form
\begin{equation}
  p(x) = 2 (x - 1)(x - 2)(x + 3).
\end{equation}
This allows us to read off the roots directly. We could imagine representing this in Python using a container containing the roots, such as
Step2:  combined with a single variable containing the leading term,
Step3:  We see that the order of the polynomial is given by the number of roots (and hence by len(p_roots)). This form represents the same polynomial but requires two pieces of information (the roots and the leading coefficient).
The different forms are useful for different things. For example, if we want to add two polynomials the standard form makes it straightforward, but the factored form does not. Conversely, multiplying polynomials in the factored form is easy, whilst in the standard form it is not.
But the key point is that the object - the polynomial - is the same
Step4:  We have defined a class, which is a single object that will represent a polynomial. We use the keyword class in the same way that we use the keyword def when defining a function. The definition line ends with a colon, and all the code defining the object is indented by four spaces.
The name of the object - the general class, or type, of the thing that we're defining - is Polynomial. The convention is that class names start with capital letters, but this convention is frequently ignored.
The type of object that we are building on appears in brackets after the name of the object. The most basic thing, which is used most often, is the object type as here.
Class variables are defined in the usual way, but are only visible inside the class. Variables that are set outside of functions, such as explanation above, will be common to all class variables.
Functions are defined inside classes in the usual way (using the def keyword, indented by four additional spaces). They work in a special way
Step5:  The first line, p = Polynomial(), creates an instance of the class. That is, it creates a specific Polynomial. It is assigned to the variable named p. We can access class variables using the "dot" notation, so the string can be printed via p.explanation. The method that prints the class variable also uses the "dot" notation, hence p.explain(). The self variable in the definition of the function is the instance itself, p. This is passed through automatically thanks to the dot notation.
Note that we can change class variables in specific instances in the usual way (p.explanation = ... above). This only changes the variable for that instance. To check that, let us define two polynomials
Step6:  We can of course make the methods take additional variables. We modify the class (note that we have to completely re-define it each time)
Step7:  We then use this, remembering that the self variable is passed through automatically
Step9:  At the moment the class is not doing anything interesting. To do something interesting we need to store (and manipulate) relevant variables. The first thing to do is to add those variables when the instance is actually created. We do this by adding a special function (method) which changes how the variables of type Polynomial are created
Step10:  This __init__ function is called when a variable is created. There are a number of special class functions, each of which has two underscores before and after the name. This is another Python convention that is effectively a rule
Step12:  Another special function that is very useful is __repr__. This gives a representation of the class. In essence, if you ask Python to print a variable, it will print the string returned by the __repr__ function. We can use this to create a simple string representation of the polynomial
Step14:  The final special function we'll look at (although there are many more, many of which may be useful) is __mul__. This allows Python to multiply two variables together. With this we can take the product of two polynomials
Step16:  We now have a simple class that can represent polynomials and multiply them together, whilst printing out a simple string form representing itself. This can obviously be extended to be much more useful.
Inheritance
As we can see above, building a complete class from scratch can be lengthy and tedious. If there is another class that does much of what we want, we can build on top of that. This is the idea behind inheritance. 
In the case of the Polynomial we declared that it started from the object class in the first line defining the class
Step17:  Variables of the  Monomial class are also variables of the Polynomial class, so can use all the methods and functions from the Polynomial class automatically
Step19:  We note that these functions, methods and variables may not be exactly right, as they are given for the general Polynomial class, not by the specific Monomial class. If we redefine these functions and variables inside the Monomial class, they will override those defined in the Polynomial class. We do not have to override all the functions and variables, just the parts we want to change
Step20:  This has had no effect on the original Polynomial class and variables, which can be used as before
Step21:  And, as Monomial variables are Polynomials, we can multiply them together to get a Polynomial
Step23:  In fact, we can be a bit smarter than this. Note that the __init__ function of the Monomial class is identical to that of the Polynomial class, just with the leading_term set explicitly to 1. Rather than duplicating the code and modifying a single value, we can call the __init__ function of the Polynomial class directly. This is because the Monomial class is built on the Polynomial class, so knows about it. We regenerate the class, but only change the __init__ function
<ASSISTANT_TASK:>
Python Code:
p_normal = (12, -14, 0, 2)
Explanation: Classes and Object Oriented Programming
We have looked at functions which take input and return output (or do things to the input). However, sometimes it is useful to think about objects first rather than the actions applied to them.
Think about a polynomial, such as the cubic
\begin{equation}
  p(x) = 12 - 14 x + 2 x^3.
\end{equation}
This is one of the standard forms that we would expect to see for a polynomial. We could imagine representing this in Python using a container containing the coefficients, such as:
End of explanation
p_roots = (1, 2, -3)
Explanation: The order of the polynomial is given by the number of coefficients (minus one), which is given by len(p_normal)-1.
However, there are many other ways it could be written, which are useful in different contexts. For example, we are often interested in the roots of the polynomial, so would want to express it in the form
\begin{equation}
  p(x) = 2 (x - 1)(x - 2)(x + 3).
\end{equation}
This allows us to read off the roots directly. We could imagine representing this in Python using a container containing the roots, such as:
End of explanation
p_leading_term = 2
Explanation: combined with a single variable containing the leading term,
End of explanation
class Polynomial(object):
    explanation = "I am a polynomial"
    
    def explain(self):
        print(self.explanation)
Explanation: We see that the order of the polynomial is given by the number of roots (and hence by len(p_roots)). This form represents the same polynomial but requires two pieces of information (the roots and the leading coefficient).
The different forms are useful for different things. For example, if we want to add two polynomials the standard form makes it straightforward, but the factored form does not. Conversely, multiplying polynomials in the factored form is easy, whilst in the standard form it is not.
But the key point is that the object - the polynomial - is the same: the representation may appear different, but it's the object itself that we really care about. So we want to represent the object in code, and work with that object.
Classes
Python, and other languages that include object oriented concepts (which is most modern languages) allow you to define and manipulate your own objects. Here we will define a polynomial object step by step.
End of explanation
p = Polynomial()
print(p.explanation)
p.explain()
p.explanation = "I change the string"
p.explain()
Explanation: We have defined a class, which is a single object that will represent a polynomial. We use the keyword class in the same way that we use the keyword def when defining a function. The definition line ends with a colon, and all the code defining the object is indented by four spaces.
The name of the object - the general class, or type, of the thing that we're defining - is Polynomial. The convention is that class names start with capital letters, but this convention is frequently ignored.
The type of object that we are building on appears in brackets after the name of the object. The most basic thing, which is used most often, is the object type as here.
Class variables are defined in the usual way, but are only visible inside the class. Variables that are set outside of functions, such as explanation above, will be common to all class variables.
Functions are defined inside classes in the usual way (using the def keyword, indented by four additional spaces). They work in a special way: they are not called directly, but only when you have a member of the class. This is what the self keyword does: it takes the specific instance of the class and uses its data. Class functions are often called methods.
Let's see how this works on a specific example:
End of explanation
p = Polynomial()
p.explanation = "Changed the string again"
q = Polynomial()
p.explanation = "Changed the string a third time"
p.explain()
q.explain()
Explanation: The first line, p = Polynomial(), creates an instance of the class. That is, it creates a specific Polynomial. It is assigned to the variable named p. We can access class variables using the "dot" notation, so the string can be printed via p.explanation. The method that prints the class variable also uses the "dot" notation, hence p.explain(). The self variable in the definition of the function is the instance itself, p. This is passed through automatically thanks to the dot notation.
Note that we can change class variables in specific instances in the usual way (p.explanation = ... above). This only changes the variable for that instance. To check that, let us define two polynomials:
End of explanation
class Polynomial(object):
    explanation = "I am a polynomial"
    
    def explain_to(self, caller):
        print("Hello, {}. {}.".format(caller,self.explanation))
Explanation: We can of course make the methods take additional variables. We modify the class (note that we have to completely re-define it each time):
End of explanation
r = Polynomial()
r.explain_to("Alice")
Explanation: We then use this, remembering that the self variable is passed through automatically:
End of explanation
class Polynomial(object):
    Representing a polynomial.
    explanation = "I am a polynomial"
    
    def __init__(self, roots, leading_term):
        self.roots = roots
        self.leading_term = leading_term
        self.order = len(roots)
    
    def explain_to(self, caller):
        print("Hello, {}. {}.".format(caller,self.explanation))
        print("My roots are {}.".format(self.roots))
Explanation: At the moment the class is not doing anything interesting. To do something interesting we need to store (and manipulate) relevant variables. The first thing to do is to add those variables when the instance is actually created. We do this by adding a special function (method) which changes how the variables of type Polynomial are created:
End of explanation
p = Polynomial(p_roots, p_leading_term)
p.explain_to("Alice")
q = Polynomial((1,1,0,-2), -1)
q.explain_to("Bob")
Explanation: This __init__ function is called when a variable is created. There are a number of special class functions, each of which has two underscores before and after the name. This is another Python convention that is effectively a rule: functions surrounded by two underscores have special effects, and will be called by other Python functions internally. So now we can create a variable that represents a specific polynomial by storing its roots and the leading term:
End of explanation
class Polynomial(object):
    Representing a polynomial.
    explanation = "I am a polynomial"
    
    def __init__(self, roots, leading_term):
        self.roots = roots
        self.leading_term = leading_term
        self.order = len(roots)
        
    def __repr__(self):
        string = str(self.leading_term)
        for root in self.roots:
            if root == 0:
                string = string + "x"
            elif root > 0:
                string = string + "(x - {})".format(root)
            else:
                string = string + "(x + {})".format(-root)
        return string
    
    def explain_to(self, caller):
        print("Hello, {}. {}.".format(caller,self.explanation))
        print("My roots are {}.".format(self.roots))
p = Polynomial(p_roots, p_leading_term)
print(p)
q = Polynomial((1,1,0,-2), -1)
print(q)
Explanation: Another special function that is very useful is __repr__. This gives a representation of the class. In essence, if you ask Python to print a variable, it will print the string returned by the __repr__ function. We can use this to create a simple string representation of the polynomial:
End of explanation
class Polynomial(object):
    Representing a polynomial.
    explanation = "I am a polynomial"
    
    def __init__(self, roots, leading_term):
        self.roots = roots
        self.leading_term = leading_term
        self.order = len(roots)
        
    def __repr__(self):
        string = str(self.leading_term)
        for root in self.roots:
            if root == 0:
                string = string + "x"
            elif root > 0:
                string = string + "(x - {})".format(root)
            else:
                string = string + "(x + {})".format(-root)
        return string
    
    def __mul__(self, other):
        roots = self.roots + other.roots
        leading_term = self.leading_term * other.leading_term
        return Polynomial(roots, leading_term)
    
    def explain_to(self, caller):
        print("Hello, {}. {}.".format(caller,self.explanation))
        print("My roots are {}.".format(self.roots))
p = Polynomial(p_roots, p_leading_term)
q = Polynomial((1,1,0,-2), -1)
r = p*q
print(r)
Explanation: The final special function we'll look at (although there are many more, many of which may be useful) is __mul__. This allows Python to multiply two variables together. With this we can take the product of two polynomials:
End of explanation
class Monomial(Polynomial):
    Representing a monomial, which is a polynomial with leading term 1.
    
    def __init__(self, roots):
        self.roots = roots
        self.leading_term = 1
        self.order = len(roots)
Explanation: We now have a simple class that can represent polynomials and multiply them together, whilst printing out a simple string form representing itself. This can obviously be extended to be much more useful.
Inheritance
As we can see above, building a complete class from scratch can be lengthy and tedious. If there is another class that does much of what we want, we can build on top of that. This is the idea behind inheritance. 
In the case of the Polynomial we declared that it started from the object class in the first line defining the class: class Polynomial(object). But we can build on any class, by replacing object with something else. Here we will build on the Polynomial class that we've started with.
A monomial is a polynomial whose leading term is simply 1. A monomial is a polynomial, and could be represented as such. However, we could build a class that knows that the leading term is always 1: there may be cases where we can take advantage of this additional simplicity.
We build a new monomial class as follows:
End of explanation
m = Monomial((-1, 4, 9))
m.explain_to("Caroline")
print(m)
Explanation: Variables of the  Monomial class are also variables of the Polynomial class, so can use all the methods and functions from the Polynomial class automatically:
End of explanation
class Monomial(Polynomial):
    Representing a monomial, which is a polynomial with leading term 1.
    explanation = "I am a monomial"
    
    def __init__(self, roots):
        self.roots = roots
        self.leading_term = 1
        self.order = len(roots)
        
    def __repr__(self):
        string = ""
        for root in self.roots:
            if root == 0:
                string = string + "x"
            elif root > 0:
                string = string + "(x - {})".format(root)
            else:
                string = string + "(x + {})".format(-root)
        return string
m = Monomial((-1, 4, 9))
m.explain_to("Caroline")
print(m)
Explanation: We note that these functions, methods and variables may not be exactly right, as they are given for the general Polynomial class, not by the specific Monomial class. If we redefine these functions and variables inside the Monomial class, they will override those defined in the Polynomial class. We do not have to override all the functions and variables, just the parts we want to change:
End of explanation
s = Polynomial((2, 3), 4)
s.explain_to("David")
print(s)
Explanation: This has had no effect on the original Polynomial class and variables, which can be used as before:
End of explanation
t = m*s
t.explain_to("Erik")
print(t)
Explanation: And, as Monomial variables are Polynomials, we can multiply them together to get a Polynomial:
End of explanation
class Monomial(Polynomial):
    Representing a monomial, which is a polynomial with leading term 1.
    explanation = "I am a monomial"
    
    def __init__(self, roots):
        Polynomial.__init__(self, roots, 1)
        
    def __repr__(self):
        string = ""
        for root in self.roots:
            if root == 0:
                string = string + "x"
            elif root > 0:
                string = string + "(x - {})".format(root)
            else:
                string = string + "(x + {})".format(-root)
        return string
v = Monomial((2, -3))
v.explain_to("Fred")
print(v)
Explanation: In fact, we can be a bit smarter than this. Note that the __init__ function of the Monomial class is identical to that of the Polynomial class, just with the leading_term set explicitly to 1. Rather than duplicating the code and modifying a single value, we can call the __init__ function of the Polynomial class directly. This is because the Monomial class is built on the Polynomial class, so knows about it. We regenerate the class, but only change the __init__ function:
End of explanation
<END_TASK>