Transcription
This video was brought to you by IND dentle IO learning Python Made Simple.
How's it going everyone? In today's video, we're going to be covering 10 Python concepts that you should know about. So, starting with the first concept, after you have downloaded Python from python.org or wherever you downloaded it from, you can create a file anywhere on your computer using the .py extension. For example, if we want to create our main script, we'll just type in `main` as the name and add the .py extension. This will make it a .py file, or a Python file, which will be executed by an interpreter in Python.
If you want to create a variable, you start by typing the variable name, followed by an equal sign, and the value that you want to assign to that name. For example, here we can type in `name` and `name` might have an age, so we can create another variable called `age`, and that's going to be 20. Now, if we ever want to use these, we can just refer to those variable names instead of hardcoding in the values or typing them in by hand each time we want to use them. For example, using the `print` statement, we can print `name`, `age`. If I can spell that, I still can't spell that, `age`. And when we run it in the console, you should see that we were able to use both of those variables, which again, is quite convenient because we can use these anywhere in the program. I mean, another example would be `print("Hello, my name is", name)` and we would get that as an output.
In Python, we have many data types, and the basic data types are integers, floats, strings, boolean, which can either be set to `True` or `False`. Then we have lists, tuples, sets, and dictionaries. An integer is any whole number, while a float is any decimal number. A string is anything that's inside quotes, and that can be either single quotes or double quotes, and it just represents text. While a boolean is used to represent two states, either `True` or `False`. Then we have lists, which can contain an arbitrary amount of elements. So, in case you have a grocery list or a list of names, we have a way of handling that kind of data. After we have tuples, which are a lot like lists, except they are immutable, which means once you set the data, you can't add anything to it or remove anything from it. While with lists, you can perform many operations such as adding elements and removing elements. After that, we have sets, which are quite similar to lists, except here you cannot have any duplicates. Everything is guaranteed to be unique. And finally, we have dictionaries, which is the basic way of representing key and value pairs. So here we have a name of Bob and an age of 20.
In Python, we have the option to use type annotations. Type annotations are completely optional, and to show you what type annotations look like, let's create a simple example such as `name = "Bob"`. This is obviously a string, but we can also tell Python explicitly that this was meant to be a string by annotating it with the string type. And we can do the same thing for the age. We'll say `age: int = 20`. But what you will notice is that if we insert something wrong, our code editor is going to give us a warning that we did not pass in the right type. And when your code gets more and more complex, these warnings become a lifesaver because, as programmers, we are bound to make mistakes eventually, whether it's because we are drunk, tired, or just not that attentive, we're bound to make a mistake eventually. And having these warnings helps a lot because if we did not specify this to be of type integer, we would not get any warning there. The code editor would not know what we were trying to do, so we would be able to do that, even if it's not what we wanted. Although, it's important to point out that even if we define this to be of type integer and we assigned it a string, the program is still going to run as normal. Type annotations do nothing in terms of how the program executes; they are just a tool for the developer. And my favorite analogy for using type annotations is the traffic light. If you think about it, when you're driving, a traffic light doesn't really do anything. It just has three colors that tell you when it's safe to drive through an intersection. It doesn't do anything at all. You can still drive through that intersection even if the lights are red. Sometimes that might go well, and other times you might end up in an accident. So, type annotations are kind of like the same thing. They do not prevent you from making terrible mistakes, but they warn you that you're about to make a mistake.
Moving on, constants. In Python, we don't really have any way of creating constants, but you can use type annotations along with the caps lock naming convention to show that it is a constant. For example, if we were to import `Final` from `typing`, we can now tell Python using type annotations that we are creating a constant. For example, we might have `version: Final[str] = "1.0.12"`. And that would be the correct way to define a constant. Even without the type annotation, you will know it's a constant by the use of uppercase characters. But unfortunately, we can still change the constant. So we can say that the `version` is now `"1.1"`, and your code editor will give you a warning only if you're using the `Final` type because this type annotation explicitly tells the developer that this value or this variable should not be overwritten. So here we're going to get that warning. But otherwise, you can even have a constant such as `pi`, which will be of type `Final[float]` and that can be `3.1415`. And that's another example of how you could create a constant in Python.
Next, we're going to be looking at how we can create reusable code in Python. So, to get started, I'm going to import `datetime` from `datetime`. And what we want to do with this is show the user the current date and time. So we're going to `print("This is the current time")` and we're going to `print(datetime.now())`. When we run this, we're going to get the following output: "This is the current time" with the current date and time. So that works perfectly fine. But if we ever want to reuse this, we're going to have to copy and paste this everywhere, which is a terrible idea because one day you might decide, "What if I wanted to type in 'This is the current date and time'?" Well, that's perfectly fine. We updated it where we had to, but the problem is it did not update in the other places, which sucks because now we need to manually waste our time going through our project, which could be thousands of lines long, to fix all the occurrences. So this is where functions come in. And in Python, to create a function, we use the `def` keyword. So here we can type `def show_date() -> None:`. And then all we need to do is indent this code inside the function, since Python uses significant indentation for its code blocks. But now, thanks to that, we can just type `show_date()` and duplicate that. And no matter how many times we use this function, it's always going to come from the same source, which means now we can type `print("This is the current date")` and remove the last bit, and it will update everywhere we use this function.
Otherwise, if you want to make a function more customizable, you can provide parameters. And to do so, we can create another function called `greet` that takes a parameter called `name` and that will be of type `str`. And once again, this will return `None`. And inside here, we can `print(f"Hello, {name}")`. So that the next time we want to use this function, we can say `greet("Bob")`, duplicate that, and `greet("Luigi")`. And that will greet both of them using the parameter that we have specified, which is really nice because, once again, if we ever need to apply any changes, we can do so here. We can say `Ciao` instead of `Hello`, and the next time we run this, we will get two outputs with that update.
And functions can even return results. For example, we might have a function called `add` that takes `a` of type `float` and `b` of type `float` because we want to add these two numbers together, and the intention with this is to return a `float`. Well, to do so, you just need to `return a + b`. And now, if we were to `print(add(1.0, 2.0))`, this function would return a result, which means calling this function will give us back the result of 3.0. And it's actually optional to define a return type. It just, once again, makes our code much more safe because if, for whatever reason, we return the string `"hello"`, our code editor is going to complain that `"hello"` was the wrong type. And if your function doesn't return anything, for example, if you're just printing `hello`, you can explicitly say that you are returning `None`, which just tells the developer that this function was only meant to be run and that they should not expect a return value from it.
Moving on, we have the concept of classes. And to break down what a class is, a class is just a blueprint for code. But let's get started by creating the main components of a class to see how it works. And in Python, to define a class, we use the `class` keyword followed by a name starting with an uppercase character. And this is how you would create a class. Now, obviously, it would be much more useful if this class could do something because all we did is insert an ellipsis as a placeholder. So what we're going to do instead of that is define an initializer. And an initializer is used to set up an instance of the class, which means we're going to create an object from this class using some specific information. And the specific information we want to use for each one of these cars in this class is what color the car should be, which will be of type `str`, and how much horsepower that car has, which will be of type `int`. And initializers return `None` by default, and they will always return `None`, but personally, I love to specify that. Now, inside the initializer, we need to assign these values to the instance of the car. And to do so, we need to use the `self` keyword. So `self.color = color` and `self.horsepower = horsepower`. Now, with that being done, we can create a car called `volvo`, which will be of type `Car`, and the car is going to be a red car with 200 horsepower. So this part here is what we refer to as an instance of the class, or an object of the class, because we are instantiating it with this specific information so that we can have a customized object. And what that means is that we can type `volvo.color` or, more fittingly, `print(volvo.color)` and also `volvo.horsepower`. We can refer to those attributes which are related to the instance. So that when we run this, we will get that information back. As you could see, the class was just the blueprint for how that car should look, and with that blueprint, we can create several cars. And it just simplifies that process. So instead of having a Volvo, or in addition, we could also have another car called `bmw`, which would be blue and it would have the horsepower of 240. Now we could `print(bmw.color)` and `print(bmw.horsepower)` and we would get the output for the BMW as well. So classes simplify the process of creating objects or code that has to be duplicated a lot because otherwise, it would be quite difficult to create many different cars that all share the same attributes or the same properties.
But I'm going to remove all of this because what we're going to do next is learn about methods. And instead of `color`, I'm going to change this to `brand`, and the brand for Volvo is obviously going to be Volvo. Anyway, with this, we can actually go down inside our class and define a function. And a method is just a function that's inside a class. Anyway, here we're going to create a method called `drive` and that's going to return `None` because what we're going to do is `print(f"The {self.brand} is driving")`. And what's important to note here is that we have `self` written everywhere, and `self` refers to the instance of the class. So here we have a Volvo, and we are referring to its attributes. So, in other words, `self` is the Volvo. And if we had a BMW, `self` would be the BMW. It refers to the instance. So if you actually want to refer to these values, you're going to have to use the `self` keyword. But we'll create one more method called `get_info` and that's going to return `None` because we're going to `print(f"{self.brand} with {self.horsepower} horsepower")`. And that's going to be our entire class. Now we have a class with some attributes and some methods. So with that, we can type `volvo.drive()` and `volvo.get_info()`. These are methods that we can use on that instance. And when we run that, we're going to see that "The Volvo is driving" and that it's a "Volvo with 200 horsepower". And this can be used with any object of this class. So if we were to have a BMW once again, which will be of type `Car`, and that was to be `bmw = Car("BMW", "blue", 240)`, we can now `bmw.drive()` as you can see, "The BMW is driving". And also, you're not limited to just using the information inside the class, but you can also define your own parameters in methods. So after the `self` keyword, anything you type in is going to be a normal parameter. And I'm just going to call this `value` of type `int` just as an example to show you that we can now use that. And to refer to it, you do not use the `self` keyword because this is included inside the method signature. So now, the next time we actually use `get_info`, we're going to have to also give it a value. And I'm going to remove the BMW and run this. So as you can see now, we have `10` and `"Volvo with 200 horsepower"`. We were able to use this information inside the method just by including it as a parameter.
But let's remove all of that because the final concept I want to talk about in this video is the concept of Dunder methods. And we're still going to have our `volvo` car for this. But uh, right now, I want to show you that if we were to `print(volvo)` as it is, what we're going to get back is a `Car` object which is located at this memory address. And that information isn't that useful to anyone who's not a programmer. What if we actually want to get back the information that's inside the car? Well, we can do so using a Dunder method. And a Dunder method is just a double underscore method. So here we're going to define the `__str__` Dunder method. And this is going to return a string. So what we're going to do is `return f"{self.brand} with {self.horsepower} HP"`. Now the next time we try to `print(volvo)`, what you're going to notice is that we're not going to get that complex object back, but we're actually going to get the string that we specified because now, in any situation, we refer to it as a string, it's going to use the `__str__` Dunder method.
And another example of a Dunder method would be if we wanted to add cars together. We could use the `__add__` Dunder method. So this is going to take `self` and `other`, which is going to be the object that we want to add to the current object. And for this example, we're just going to return a string, why not? So what we're going to do is `return f"{self.brand} and {other.brand}"`. And if you actually want your code editor to give you suggestions for this, you would have to annotate this as `other: Car`. And that will lead you to this code completion which contains `brand` and `horsepower`. Anyway, to make this work, we're going to have to create another car called `bmw` of type `Car`, which will equal `Car("BMW", "blue", 240)`. Next, we can `print(volvo + bmw)`. And this Dunder method is going to take care of the functionality of the plus sign, which means that when we run this, we're going to get `"Volvo and BMW"` as an output. Without this Dunder method, Python's not going to know how to handle this because it does not define the `__add__` Dunder method. And it actually warns you about that. But if we were to run it anyway, once again, it would not know how to do that. So you can choose to define that in your class, and then it will work properly. And it's worth mentioning that there are a lot of Dunder methods worth researching because, obviously, maybe in the future, you will want to multiply cars. So for all of these operations, there's a Dunder method for them, such as `__mul__`. And once you define that, you'll be able to use this comfortably.
But yeah, those were 10 Python concepts that are quite important, and this video was just an introduction to them. I still recommend you study these concepts more thoroughly, do some more research online, watch some other YouTube videos regarding them. But otherwise, it should have been a great starting point for how the concepts in Python actually work. So yeah, let me know in the comment section down below whether you have any other questions or whether some concepts should have had some more explanation. But otherwise, with all that being said, as always, thanks for watching, and I'll see you in the next video.