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

Comparing .__call__() and .__init__()

Resource mentioned in this lesson: Python’s Magic Methods: Leverage Their Power in Your Classes

00:00 Before moving on to more advanced uses of .__call__(), let’s step back and compare it with .__init__() because it’s easy to get them confused.

00:07 They’re both special methods that determine how Python handles your custom classes. And understanding special methods is key to working with the Python data model, as they are what defines the contract between your objects and the Python interpreter.

00:22 .__init__() is the instance initializer. It runs to initialize a newly-created object. Its purpose is to set up or configure the new instance.

00:31 As such, it’s only ever run once per instance, and it receives arguments that are passed when calling the class constructor. .__call__() turns instances into callable objects.

00:43 It runs when invoking an object. This makes it part of the object’s behavior, just like other methods, and it receives arguments passed when calling class instances. There are tons of special methods and attributes.

00:56 If you’re interested in exploring them further, check out Python’s Magic Methods: Leverage Their Power in Your Classes. Or hang out with me, open the REPL, and we’ll go through a quick example.

01:09 To illustrate the differences between these two special methods, create a class called Demo. Define .__init__() with the parameters self and attr. def __init__() (self, attr):

01:24 The first thing .__init__() will do is print() the f-string, f"Initialize an instance of {self.__class__.__name__}".

01:32 And here’s a couple more special attributes. .__class__ stores a reference to .self’s class, and .__name__ stores the class’s name as a string. Assign attr to self.attr, and print() the f-string, f"{self.attr=}", in curly brackets.

01:49 This argument to the f-string is a handy shorthand for printing a variable name and its value in a debug-style format. Now define .__call__() with the parameters self and arg. def __call__(self, arg):

02:05 print() the f-string, f"Call an instance of {self.__class__.__name__} with {arg}".

02:18 Now to see this in action, initialize an instance of Demo.

02:22 demo_instance = Demo() passing in the string, "Some initial value", and what’s printed is, Initialize an instance of Demo, self.attr = "Some initial value".

02:35 This was everything in .__init__(). And now when you call this demo_instance,

02:40 demo_instance("Hello").

02:44 You see, Call an instance of Demo with Hello, coming directly from your .__call__() method. This should really clear up the differences between these two. .__call__() runs when you call a concrete instance like demo_instance, and it lets you treat those instances like functions.

03:01 This functionality won’t be available unless the class defines .__call__() explicitly or receives it through inheritance. In contrast, .__init__() is an essential part of instance creation.

03:12 It can be defined explicitly like you did with Demo, but even if .__init__() isn’t included in your class definition, it will still be inherited from the built-in object class, because every class in Python inherits from object. And you can confirm that object does have a .__init__(), similar to how you did before: __init__() in dir(object) returns True. Alright, with those finer details cleared up, you’re ready to explore more advanced uses of .__call__().

03:41 Meet me in the next lesson.

Become a Member to join the conversation.