Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Class Creation

00:00 In the previous lesson, I explained why you might want to write object-oriented code. In this lesson, I’ll introduce you to how to do just that in Python.

00:10 You saw the PosixPath class and how to instantiate it into an object in the lesson prior to this. That instantiation process is done by calling the class with parentheses.

00:22 The constructor call can take arguments, which most often are the initial values for attributes that you want to keep in the object. Python uses a special method called .__init__(), which it calls as part of the class instantiation process.

00:37 You may hear programmers refer to this as the constructor. That isn’t technically correct, but it’s similar enough to constructors in other languages that it isn’t really worth debating. When you write your own class, you likely are going to need to write a .__init__() method to set it up.

00:54 All of an object’s methods, including .__init__(), automatically get called with a reference to the object as their first argument. When you declare a method, you have to include this argument in the signature. By convention, in Python, this is known as self. Technically, you can call it anything you want.

01:13 The compiler doesn’t enforce the name, but your fellow programmers may beat you, enforcing the convention through a different kind of argument. Let’s go revisit our circle from the previous lesson, this time as a class.

01:30 Logically enough, to declare a class, you use the class keyword. Here I have declared a class called Circle. The colon declares a block, and like with other blocks in Python, everything indented under here is part of the declaration.

01:50 By convention, the initialization method, .__init__(), is typically included first. As you can see here, the first argument to the method, like all methods, is self. When it’s called, it will contain a reference to the object.

02:06 Remember when I said .__init__() strictly isn’t a constructor? This is why. When this method is called, the object has actually already been constructed. It’s being passed into the .__init__() to be initialized by you.

02:20 This is also why .__init__() doesn’t have to return anything. The object already exists, and you can do anything to it when it’s passed in as self. A true constructor in the strictest sense actually has to return the newly constructed object. The second argument here to .__init__() is specific to my Circle class, and it’s the radius of the circle.

02:45 Note that the argument being passed into the initializer doesn’t mean it’s part of the object. To do that, you have to actually assign it to the object.

02:53 As self is a reference to the object being initialized, you use dot notation to store it. I’ve gone with the typical pattern of naming the object attribute the same thing as that passed into the method, but there’s no requirement to do this.

03:08 This little chunk of boilerplate code actually kind of annoys me. Pretty much every single class you write is going to have this, and if you’ve got several arguments, you can have several lines of code devoted to this.

03:20 I feel like there should be an easier way to do this, but don’t quite know what it would look like. This isn’t just a Python thing. Most object-oriented languages have a similar setup.

03:31 So, that’s .__init__(). How about another method?

03:36 Pardon the lack of spacing here. Normally, you put an empty line between methods, but in the REPL, you can’t do that. This line is the declaration of a method called .area().

03:47 Like all methods in a class, it takes the self argument, and in this case, no others. The

03:57 code for .area() implements the famous πr² formula. Notice how it is accessing the radius stored on the object using self.radius.

04:07 Again, this is possible because self is being passed into the method and is the object. That’s enough for your first class. Let’s use it to create a circle object.

04:21 By using parentheses on the class, I’m calling it. Calling the class constructs an object. Because .__init__() took one argument in addition to self, the constructor here expects a value.

04:33 If I hadn’t put the 3 in this code, I’d get an error. As I did put 3, it gets passed into the constructed object, which then calls .__init__(), passing the argument along. The .__init__() then stores the 3 in the self.radius attribute. Once .__init__() is done, the constructor returns the new object, which I’ve stored in the variable named small.

04:59 I can access the attributes on the object by using dot notation,

05:05 and similar for calling a method. Note that self isn’t used in the invocation of this method. Calling a method on an object automatically passes the object to the method.

05:16 As the .area() method doesn’t take any other arguments, it is invoked without any at all. Let’s create another circle.

05:26 This time, the radius is a bit bigger,

05:30 and you can see its attribute.

05:34 And of course, I can calculate the area on this one as well. Attributes are also editable

05:43 Here, I’ve set the radius to a new value. When I recall .area(),

05:49 a new result is calculated. Similar to the whole self thing, the style you use when you write a class isn’t enforced by the compiler, but it is best to stick with known practices.

06:02 It’s easier for someone else to understand your code if you use the conventions that everyone else does. First off, like variables, attributes on an object use snake case. That’s all lowercase, with words separated by underscores. Class names themselves use pascal case. That’s no underscores, but with capitals on each word.

06:25 If you’re coming from other object-oriented languages, you might be wondering about things like private, protected, and public permission structures. If you’re not coming from other languages, these concepts control who can see the attribute or call a method—the object, inheritors, or anyone. Python doesn’t really have this concept.

06:44 It uses a suggested convention based on naming instead. If you are coming from another language, this might seem like a bit of a shock. It took me a little to adjust the idea as well.

06:55 I used to write code where I tried my best to protect programmers from themselves, trying to stop them from doing things they weren’t supposed to. Python’s attitude is a little more permissive. It essentially says, Hey, this is dangerous, but if you know what you’re doing, we’re all adults here. So, how do you signal danger? Well, Python has public and non-public members.

07:16 Non-public members are indicated by putting an underscore in front of their names. This isn’t enforced in any way. People using the object can touch these attributes and call these methods, but you’re warning them that they’re not really part of the public-facing interface, and they might change. How intensely you use these ideas is kind of a style thing.

07:38 I don’t tend to use non-public values very much unless there’s an important reason why. For example, if I’ve got two values that must be set together, I might store them with underscores and provide a method for changing them.

07:49 At the same time, I have come across code that’s the other way though. One of the libraries I’ve contributed to once in a while is called Asciimatics. It’s a terminal-based animation and TUI builder.

08:01 The core maintainer really likes his non-public attributes. Pretty much everything is non-public, with special mechanisms for exposing the API. I haven’t actually had a chat with him as to why, but I suspect he used to write object-oriented code in another language and has carried the habit over.

08:19 You’ve already seen .__init__(), and I’ve spoken about other special methods denoted by their double underscores. Key functionality provided by Python in classes is mostly built using these kinds of methods. Although they are a system thing, there’s nothing stopping you from using the same mechanism.

08:37 Do note that how they show up is a bit different though. Any member with two leading underscores gets renamed. It kind of hides the member hides—as in, makes it less obvious.

08:50 You can still do whatever you like with it, but you can think of it as an extra-special warning. If the single underscore is a note in the manual saying you probably shouldn’t do that, this is the sticker sealing your device shut, saying voids warranty. The sticker doesn’t stop you, but you really should know what you’re doing.

09:09 These special methods get renamed by Python. This renaming is called name mangling. For example. .__member gets renamed as .__ClassName__member.

09:21 Just like with the single underscore, I can get at it if I know the name, but you can’t really claim ignorance if you’re going hunting for things. This works for both attributes and methods.

09:34 Next up, I’ll dive into the various ways you can associate data with a class, covering more information about attributes.

Avatar image for amandajones700

amandajones700 on Oct. 31, 2023

You could use the @dataclass decorator as a cleaner way of setting variables e.g.

from dataclasses import dataclass

class Dog:
    name: str
    age: int

rather than

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Oct. 31, 2023

Hi Amanda,

Thanks for the comment. Dataclasses are covered in part 2 of the course. Hope you enjoy it.

Become a Member to join the conversation.