Page 1 of 2 Python is an object-oriented language, but you can get away with igoring this fact. However, if you do you are missing out on some of its best features. Find out about Python with class. This extract is from my book that explores the features that make Python special and "Something Completely Different".
In the previous chapter we looked at how Python provides the basic facilities needed to implement what looks like a classical approach to objects – that is class and methods. In this chapter we look more closely at how this works. It is slightly more complicated than you might imagine.
Programmer's Python Everything is an Object Second Edition
Is now available as a print book: Amazon

Contents
- Get Ready For The Python Difference
- Variables, Objects and Attributes
- The Function Object
- Scope, Lifetime and Closure
Extract 1: Local and Global
- Advanced Functions
- Decorators
- Class, Methods and Constructors
Extract 1: Objects Become Classes
- Inside Class ***NEW!
- Meeting Metaclasses
- Advanced Attributes
- Custom Attribute Access
- Single Inheritance
- Multiple Inheritance
- Class and Type
- Type Annotation
- Operator Overloading
- Python In Visual Studio Code
Extracts from the first edition
<ASIN:1871962749>
<ASIN:1871962595>
<ASIN:1871962765>

You don’t have to know how class does its magic generally and to create objects to use object-oriented programming but if you do know how it works there many more possibilities open to you. Python exposes much of the mechanism of objects so that you can modify the way that it works.
This is often called meta-programming but where ordinary programming ends and meta-programming begins is a fairly arbitrary line and in Python you can do meta-programming in Python.
Most of the work of the class is done by a set of magic methods. In this chapter the focus is on those methods that are involved in creating an object without the complication of inheritance – for that see Chapter 11.
__new__ and __init__
We have already met __init__ as a way of creating instance attributes and generally initializing the instance. When you call a class as a function __init__ is run just after another magic method, __new__.
It is confusing at first to have two methods involved in creating an instance but it is perfectly logical.
-
The __new__ class method is responsible for creating an object of the class. It brings the instance object into existence.
-
Then __init__ is called to initialize the instance by adding instance attributes and assigning values.
This is all fine, but what exactly does it mean that __new__ is used to create an instance of the class?
We haven’t gone into detail about inheritance in Python, but the basic idea is that every class has a superclass, i.e. the class that it is based on. If you don’t specify otherwise, every class has the class object as its superclass.
Unlike other object-oriented languages, an initial instance in Python is very simple – it has no attributes of its own. Any attributes that it appears to have come from its class object and the class object’s superclass and so on. Instances only gain attributes when the __init__ method adds them or when an attribute is assigned to.
Recall that at first all of the attributes that an instance seems to have are hosted by its class object. What this means is the __new__ simply returns a “blank” instance object. To indicate that this “blank” object is an instance of the class in question, it has its __class__ attribute set to the class. This is the only thing that distinguishes it as an instance of this class rather than some other class.
Once the blank instance object has been created, the class __init__ method is called to add instance attributes and hence customize it. You can think of the default __new__ method as:
def __new__(cls):
return object.__new__(cls)
where object is the start of the inheritance hierarchy, see later, and it is the object that everything else derives from. It has a __new__ method that will return a basic object with its __class__ set to cls.
The parameter that you pass to __new__ has to be a type object, which for the moment we can take to be a class or a built-in type. It plays a very minor role in the creation of the instance, but you will find that it is stored in the __class__ attribute of the instance. It is used to determine what class the instance is an instance of. In most cases you simply pass the default parameter, cls in this case.
If the class is called with any additional parameters these are passed on to __new__ following the first parameter, as in the case of __init__.

For example:
class MyClass:
def __new__(cls):
print(cls)
return object.__new__(cls)
myObject=MyClass()
print(myObject.__class__)
print(myObject.__dict__)
This explicit definition of __new__ does what the default would do. You can see that you have a “blank” instance as its __dict__ is empty and its __class__ is set to MyClass.
If you don’t want to perform the default creation of an instance you don’t have, for example we can confuse the instance creation by setting __class__ to something other than cls:
class MyClass2:
pass
class MyClass:
def __new__(cls):
return object.__new__(MyClass2)
myObj=MyClass()
print(myObj.__class__)
Notice that now we have passed MyClass2 to the call to __new__ which results in __class__ being set to MyClass2, even though the instance was created by MyClass.
In Python type is a matter of keeping track of what created the instance and it is largely based on an honor system. If you want to break the rules and lie about what created the instance you can.
The type that is assigned doesn’t change very much about the way things actually work. The one thing it does change is whether or not __init__ is called. As long as __new__ returns an object of the indicated type, or subtype, then __init__ is called, but if it returns anything else __init__ is not called. That is, the system only calls __init__ if the __class__ of the instance created by __new__ matches the class used to create it.
So in the previous example where object.__new__(MyClass2) was used, they don’t match and __init__ isn’t called. However, in:
class MyClass:
def __new__(cls):
print("__new__ called")
return object.__new__(cls)
def __init__(self):
self.MyAttribute = 20
print("__init__ called")
myObj=MyClass()
you will see the __init__ called and MyAttribute will exist in the instance’s __dict__ and be set to 20.
 Finally, both __new__ and __init__ are passed any arguments used when the class is called as a function. This means you have to include the parameters, even if you don’t use them, for example:
class MyClass:
def __new__(cls,value):
return object.__new__(cls)
def __init__(self,value):
self.MyAttribute = value
myObj=MyClass(42)
In this case MyAttribute is set to the value passed to the constructor i.e. 42.
|