Exploring the Instantiation Process

For more information on properties, you can check out Python’s property(): Add Managed Attributes to Your Classes.

00:01 Exploring the Instantiation Process. To explore how Python’s instantiation process works internally, consider the following example of a Point class that implements custom versions of both the .__new__() and .__init__() special methods, for demonstration purposes.

00:20 This line defines the Point class using the class keyword followed by the class name.

00:29 This defines the .__new__() special method, which takes the class as its first argument. Note that using cls as the name of this argument is a strong convention in Python, just like using self to name the current instance is.

00:43 The method also takes *args and **kwargs, which allow for passing an undefined number of initialization arguments to the underlying instance. This line prints a message when .__new__() runs the object creation step.

01:00 This complex-looking line creates a new Point instance by calling the parent class’s .__new__() method with cls as an argument. In this example, object is the parent class, and the call to super() gives you access to it.

01:14 Then the instance is returned. This instance will be the first argument to .__init__(). Here you define .__init__(), which is responsible for the initialization step.

01:26 This method takes a first argument called self, which holds a reference to the current instance. The method also takes two additional arguments, x and y.

01:37 These arguments hold initial values for the instance attributes .x and .y. You need to pass suitable values for these arguments to the call to Point(), as you’ll learn in a moment. These lines print a message when .__init__() runs the object initialization step, and then initialize the .x and .y attributes, respectively. To do this, they use the provided input arguments x and y. Finally, these lines implement the .__repr__() special method, which provides a proper string representation for the Point class.

02:10 With Point in place, you can uncover how the instantiation process works in practice. Save the code to a file called point.py and start a new Python session from a command-line window in the same directory as the code is saved.

02:26 Then run the code seen on-screen.

02:34 Calling the Point() class constructor creates, initializes, and returns a new instance of the class. This instance is then assigned to the variable point.

02:45 In this example, the call to the constructor also lets you know the steps that Python internally runs to construct the instance. First, Python calls .__new__() and then .__init__(), resulting in a new and fully initialized instance of Point, as you confirmed at the end of the example.

03:03 To continue learning about class instantiation in Python, you can try running both steps manually.

03:13 Here, you first call .__new__() on the Point class, passing the class itself as the first argument to the method. This call only runs the first step of the instantiation process, creating a new and empty object. Note that creating an instance this way bypasses the call to .__init__(), and the object is not initialized.

03:34 This can be demonstrated by trying to access the .x and .y attributes, which generate errors. Once you have the new object, then you can initialize it by calling .__init__() with an appropriate set of arguments.

03:52 After this, the Point object is properly initialized, with all of its attributes set up. Note that this code is intended to be a demonstration of how the instantiation process works internally.

04:03 It’s not something that you would typically do in real code. A subtle and important detail to note about the .__new__() special method is that it can also a return an instance of a class different from the class that implements the method itself. When that happens, Python doesn’t call .__init__() in the current class, because there’s no way to unambiguously know how to initialize an object of a different class.

04:29 Next, you’ll see an example of this on-screen, where the .__new__() method of the B class returns an instance of the A class.

05:11 Because B.__new__() returns an instance of a different class, Python doesn’t run B.__init__(). To confirm this behavior, save the code into a file called ab_classes.py and then run the following code in an interactive Python session.

05:32 The call to the B() class instructor runs B.__new__(), which returns an instance of A instead of B. And this is why B.__init__() never runs.

05:44 Note that b doesn’t have a .b_value attribute. In contrast, b does have an .a_value attribute with a value of 42.

05:54 This instance can be used to check if b is a member of a given class. Note that it is not a member of class B, but it is a member of class A.

06:11 Now that you know the steps that Python takes internally to create instances of a given class, you’re ready to dig a little deeper into other characteristics of the .__init__() and .__new__() special methods and the steps that they run. So in the next section, you’ll start that off by looking at .__init__().

Become a Member to join the conversation.