 In this video, we're going to look into the so-called numerical tower, and this is an extension to the video earlier that we did on abstract versus concrete data types. So in the previous video, we only said that, okay, there are concrete data types, and we can classify them into so-called abstract data types by looking into common behavior. So what do different concrete data types have common in terms of behavior, and then they are grouped together into one abstract data type. Now we will look into a way of how we can formalize this a bit more using Python. This is a bit of a more theoretic lecture, but it gets a couple of points across of how to think when you want to become good at Python. So let's go ahead and create a new notebook and call it numerical tower. And let me first go ahead and paste in some diagram of something that you should know from your high school math courses. So what do we see here? We see a couple of circles where the inner circle is always a perfect or a true subset of the outer circle. And in the inner most circle, we have the set of so-called natural numbers n, which as an example, we see the numbers 0 and 1 in there. So what is not in there are negative numbers. And then we have the set at the set set, which is the set of whole numbers. So it's not only natural numbers, but whole numbers, so to say. And the addition to the set n is simply the negative number. So if we look at the number 0, 1, minus 1, and so on, if you look at the group of them together, we call that set. And then we don't see that we don't have any fractions in here. So in the next level, we are going to add fractional numbers, which we call rational numbers in mathematics. And the abbreviation is the so-called set Q. And Q has here, for example, it has different kinds of numbers. So any number that can be expressed as a fraction, of course, is part of that positive or negative. And also any number like 1.333 and so on, which is really like 4 over 3 here. So then in the next set, we add the so-called irrational numbers. So numbers like the square roots, e, pi, and so on. And we call that set the real numbers. And then there is one more set, which is the set of complex numbers. Complex numbers are not such a big deal in real life, in a practitioner's life. But let's say you want to truly go into the details of data science, of machine learning, for example. I suggest you go ahead and study some linear algebra. And in order to do so deeply, then it's quite useful to know complex numbers as well. So what is this here? So why am I giving you this picture here? Well, this picture is kind of a classification. So just as we, in the previous video, classified concrete data types into abstract ones. What we do here is we also have a classification, but we are classifying different kinds of numbers. And then the different classifications, they also have a certain relationship among each other. And in this case, that is what is called the numerical tower, the natural numbers are a perfect subset of the whole numbers. The whole numbers are a perfect subset of the fractional numbers, the rational numbers, and so on. So it's always a perfect subset of the next layer, so to say. So how can we model that in code? So we have, if we look into the two innermost circles, so the natural numbers and the set of whole numbers, these numbers in Python, we could express as simply the integer type. So if I say one or also negative one, these are, of course, examples of whole numbers. So there's no data type, no concrete data type that is there to model only natural numbers, but the integer data type more or less is the sum of, or it's basically the outer circle here. So it's the circle set of only the whole numbers. And the natural numbers are, of course, part of that. Now we want to model rational numbers. So one way to do that with the built-in data types that we have seen is we could use basically fractions, so to say, by using the division operator. The problem here is one over three gives us back a floating point number. And we saw in a previous video that floating point numbers are inherently imprecise. So what we could think of, we could say that the floating point numbers are kind of an approximation of the set of rational numbers, but really they are not. Because we can have other rational numbers or other floating point numbers, I want to say. For example, if we import the math module, we could look at math.py and that is also a floating point number, and this is, of course, an irrational number. So something that we cannot express using simple fractions of integers or whole numbers. Therefore, the floating point data type is not a good model of the rational numbers. In particular, the floating point data type is really a model of the real numbers, which includes the rational numbers, but it's not only the rational numbers. And then also, just briefly, I want to mention how we can create complex numbers in Python. We use the J notation. So if I want to say the number one, so the complex number with a real part of one and an imaginary part, also let's say one, we write it like this, one plus one J. And this is the complex number with real and imaginary part of one. And so these are different data types, and the complex data type is also a type on its own, the complex data type. Now the question is, how can we classify the concrete numeric data types? So what we can do in Python is we can go to the so-called numbers module. So let's go to the Python documentation, the library reference, and let's look for the numbers module here. And it's right above the math module, which we know already. So if I click on the numbers module, what we see here is a class called number, uppercase number here. And then we see a class uppercase complex, the numeric tower begins here. Then we have a class called uppercase real for the real numbers, then rational for the rational numbers, and so on. Integral are basically the whole numbers, so including the natural numbers, of course. So what we could do is we could use them in Python code, so let's say we could import the numbers module, and the numbers module, as I just said, has these classes in it. So for example, let's say real for the real numbers. Now what can we do with these classes? Well, first and foremost, we have to understand that these classes here, they are not constructors. So they are not like the built-in int or float constructors. We cannot construct objects out of them. There are so-called abstract base classes. Abstract kind of implying the notion of an abstract data type. But an abstract base class is really just a blueprint of what all the different concrete objects that are of this type that adhere to this base class, to this abstract base class, must satisfy. So what we see here is, for example, any complex number must have a real and an imaginary attribute. And we see a couple of small things. So the real class here says that it has to support conversion to a float. So this must be supported. It has to support the round function and so on. So in other words, this is also a summary, so to say, of different behaviors that numbers in this case, only numbers, can have or may not have. And what we could do is, we could go ahead and use these abstract base classes, in this case the real class, together with the instance built-in function. So if I say, I take the integer 7 and I ask the question, is the integer 7 an instance or an example of the real abstract base class? And the answer is true. And the reason why this makes sense, first and foremost, is the real base class, the real abstract base class, is an abstraction. So it's a classification for the second innermost circle here. So this one here, the rational numbers. And of course, the integer that we just used is, as it is a natural number, it is also included in the set of rational numbers. Therefore, and also in the set of real numbers, I wanted to say, so therefore we get back a true. So now the next question is, is the number 7 an example of a complex number? And the answer is, of course, true for the same reason. All the complex numbers, they also incorporate the idea of whole numbers. So therefore, any concrete data type, like the int data type that models whole numbers, also adheres to the abstract blueprint, so to say, of what all complex numbers have in common. And in really, you have to see this from the inside out. So in other words, the most generic kind of number you can have in Python is basically the complex number or a complex number in the abstract sense, not in the concrete sense. And the natural number is basically the most specific number you can have. So this is kind of like the ordering here, so to say, from most generic to least generic. And we can use these base classes to ask the question in abstract terms does the object in this case 7 fulfill certain properties or does it have a certain exhibit a certain behavior and namely the behavior that is written down here in the numbers module, right? So that is the first example of an abstract base class. We will see further abstract base classes in future videos when we talk about the other abstract data types that I introduced in the previous video like a sequence and collections and these kind of things. But this video only regards numeric data types, numeric tower. So one way this could be useful is remember the video we did on input validation and one of the strategies was type checking. So in the type checking strategy, what we do is we check if the input to a function, for example, is of a certain type of a certain concrete data type. And sometimes what you want to do is you want to you want to enable so-called duck typing. So you want to enable that a function. Let's say let's go ahead and use as an example the round function. So the round function, for example, takes an integer as its argument. It accepts it. It can work with it. It does. It may not make sense because we know that rounding the integer gives us just back the same integer. However, it supports the operation in abstract terms. So if I go ahead and let's say if I round one over three, I get back zero. So let's say let's give it three decimals. So I get back 0.333. The round function also supports flow. So we see that the round function supports integers, but it also supports floating point number. So the result of one over three. So let's check why is that? Well, as we see here, the round function must be supported by anything that is regarded a real number in the abstract sense. So what we can conclude from that is the only numbers that do not support the round function are complex numbers. So let's try that. Let's try that for now. So let's go ahead and use the round function and let's pass to it 1 plus, where is it? 1 plus 1j. And let's see what happens and we get a type error. And it says complex number doesn't define blah, blah, blah, round method. We don't really care about the details of the error message so far, but really what that means is it doesn't make sense in the abstract sense to round a complex number. And the reason being is complex numbers are really two dimensional numbers and rounding always needs to work with numbers that only have one dimension on the real axis, so to say. And complex numbers don't have just one axis. So to say they have two dimensions, therefore the round function doesn't work with it. So if you ever want to build a function that accepts different kinds of numbers that adhere to the same abstract idea, then what you could do when you implement a type checking strategy, instead of hard coding some concrete data type, let's say like int or float in the instance function call here, you could use instead the abstract base class here, like for example real or complex, and that does the same check, but it is a more broader, it is a broader check, so again the real check here basically allows as we see the number 70 integer, but it would also allow, let's do that here, the floating point number 7.0, this is also true. So if you only want to check that the arguments that are passed with some function that you implement adhere to some notion in the abstract sense simply replace the instance check, the type checking check with a way you usually would use a concrete data type just replace it with the abstract base class here, so the abstract data type, so to say. So in other words what we learned from that is that the so-called ABCs, abstract base class are really the model that we use for abstract data types. And we will see a couple of more of them. So now this is probably one of the more theoretic videos in this course, but I still want you to get the idea that all of the theoretic concepts, the classification schemes, the abstract data types, they are actually written down somewhere in the documentation, in particular in, for example, a numbers module regarding numbers, but also in another module where all the other abstract base classes are summarized. And all the notions that are developed in this course they are really founded on the Python documentation. So when I said earlier in the other video that it makes sense to know these terms, it makes sense because it enables you to read the documentation a lot clearer. It makes it easy to understand the documentation. And whenever you do that, just to give you one word, if you do a type checking strategy for input validation and you use an abstract base class, we call that goose typing, so to say. Why is called goose typing not so important? But remember duck typing, the word duck typing really means that two objects of a different data type they walk and quack alike. So in this case, the 7 and the 7.0 they behave in the same way in some context. Therefore they walk and quack alike. So they are the same objects, so to say, the same type in terms of duck typing. And goose typing is the notion that we basically use an abstract base class to make this check work. Okay, so this is a little bit of theory. I know maybe you don't like too much theory here, but you will see that sometimes in code that this is useful. Okay, so I will see you in the next video.