Classes are objects in Python.

I like to take examples from one programming language and attempt to use them in another.

I asked myself if Python code could be written to imitate JavaScript's prototypical inheritence where prototypical inheritence is defined as:

…when an object inherits from another object. This differs from classical inheritance, in which a class inherits from another class.

https://www.quora.com/What-is-prototypal-inheritance

Classes are in indeed objects in Python so in a way Python classes are objects that ineed do, too, inherit from other objects. It just isn't implemented exactly the same in JavaScript.

I did find a blog post about writing a Python library that has objects behaving like JavaScript objects.

The material below is adapted from from What is a metaclass in Python?

Python has a very peculiar idea of what classes are, borrowed from the Smalltalk language.


In [131]:
from pprint import pprint

In [132]:
%%HTML
<p style="color:red;font-size: 150%;">Classes are more than that in Python. Classes are objects too.</p>


Classes are more than that in Python. Classes are objects too.


In [133]:
%%HTML
<p style="color:red;font-size: 150%;">Yes, objects.</p>


Yes, objects.


In [134]:
%%HTML
<p style="color:red;font-size: 150%;">As soon as you use the keyword class, Python executes it and creates an OBJECT. The instruction</p>


As soon as you use the keyword class, Python executes it and creates an OBJECT. The instruction


In [135]:
class ObjectCreator(object):
    pass

creates in memory an object with the name "ObjectCreator".


In [136]:
%%HTML

<p style="color:red;font-size: 150%;">This object (the class) is itself capable of creating objects (the instances), and this is why it's a class.</p>


This object (the class) is itself capable of creating objects (the instances), and this is why it's a class.

But still, it's an object, and therefore:

  • you can assign it to a variable

In [137]:
object_creator_class = ObjectCreator
print(object_creator_class)


<class '__main__.ObjectCreator'>
  • you can copy it

In [138]:
from copy import copy
ObjectCreatorCopy = copy(ObjectCreator)
print(ObjectCreatorCopy)
print("copy ObjectCreatorCopy is not ObjectCreator: ", ObjectCreatorCopy is not ObjectCreator)
print("variable object_creator_class is ObjectCreator: ", object_creator_class is ObjectCreator)


<class '__main__.ObjectCreator'>
copy ObjectCreatorCopy is not ObjectCreator:  False
variable object_creator_class is ObjectCreator:  True
  • you can add attributes to it

In [139]:
print("ObjectCreator has an attribute 'new_attribute': ", hasattr(ObjectCreator, 'new_attribute'))


ObjectCreator has an attribute 'new_attribute':  False

In [140]:
ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
print("ObjectCreator has an attribute 'new_attribute': ", hasattr(ObjectCreator, 'new_attribute'))


ObjectCreator has an attribute 'new_attribute':  True

In [141]:
print("attribute 'new_attribute': ", ObjectCreator.new_attribute)


attribute 'new_attribute':  foo
  • you can pass it as a function parameter

In [142]:
def echo(o):
    print(o)

In [143]:
# you can pass a class as a parameter
print("return value of passing Object Creator to {}: ".format(echo), echo(ObjectCreator))


<class '__main__.ObjectCreator'>
return value of passing Object Creator to <function echo at 0x7fa9d0059048>:  None

In [144]:
%%HTML

<p style="color:red;font-size: 150%;">Since classes are objects, you can create them on the fly, like any object.</p>


Since classes are objects, you can create them on the fly, like any object.


In [145]:
def get_class_by(name):
    class Foo:
        pass
    class Bar:
        pass
    classes = {
        'foo': Foo,
        'bar': Bar
    }
    return classes.get(name, None)

In [146]:
for class_ in (get_class_by(name) for name in ('foo', 'bar', )):
    pprint(class_)


<class '__main__.get_class_by.<locals>.Foo'>
<class '__main__.get_class_by.<locals>.Bar'>

But it's not so dynamic, since you still have to write the whole class yourself.

Since classes are objects, they must be generated by something.

When you use the class keyword, Python creates this object automatically. But as with most things in Python, it gives you a way to do it manually.

Remember the function type? The good old function that lets you know what type an object is:


In [147]:
print(type(1))


<class 'int'>

In [148]:
print(type("1"))


<class 'str'>

In [149]:
print(type(int))


<class 'type'>

In [150]:
print(type(ObjectCreator))


<class 'type'>

In [151]:
print(type(type))


<class 'type'>

Well, type has a completely different ability, it can also create classes on the fly. type can take the description of a class as parameters, and return a class.


In [152]:
classes = Foo, Bar = [type(name, (), {}) for name in ('Foo', 'Bar')]

In [153]:
for class_ in classes:
    pprint(class_)


<class '__main__.Foo'>
<class '__main__.Bar'>

type accepts a dictionary to define the attributes of the class. So:


In [154]:
classes_with_attributes = Foo, Bar = [type(name, (), namespace) 
                                      for name, namespace 
                                      in zip(
                                             ('Foo', 'Bar'), 
                                             (
                                              {'assigned_attr': 'foo_attr'}, 
                                              {'assigned_attr': 'bar_attr'}
                                             )
                                             )
                                      ]

In [155]:
for class_ in classes_with_attributes:
    pprint([item for item in vars(class_).items()])


[('__doc__', None),
 ('assigned_attr', 'foo_attr'),
 ('__dict__', <attribute '__dict__' of 'Foo' objects>),
 ('__module__', '__main__'),
 ('__weakref__', <attribute '__weakref__' of 'Foo' objects>)]
[('__doc__', None),
 ('assigned_attr', 'bar_attr'),
 ('__dict__', <attribute '__dict__' of 'Bar' objects>),
 ('__module__', '__main__'),
 ('__weakref__', <attribute '__weakref__' of 'Bar' objects>)]

Eventually you'll want to add methods to your class. Just define a function with the proper signature and assign it as an attribute.


In [156]:
def an_added_function(self):
    return "I am an added function."

In [157]:
Foo.added = an_added_function
foo = Foo()
print(foo.added())


I am an added function.

You see where we are going: in Python, classes are objects, and you can create a class on the fly, dynamically.


In [158]:
%%HTML
<p style="color:red;font-size: 150%;">[Creating a class on the fly, dynamically] is what Python does when you use the keyword class, and it does so by using a metaclass.</p>


[Creating a class on the fly, dynamically] is what Python does when you use the keyword class, and it does so by using a metaclass.


In [159]:
%%HTML
<p style="color:red;font-size: 150%;">Metaclasses are the 'stuff' that creates classes.</p>


Metaclasses are the 'stuff' that creates classes.

You define classes in order to create objects, right?

But we learned that Python classes are objects.


In [160]:
%%HTML

<p style="color:red;font-size: 150%;">Well, metaclasses are what create these objects. They are the classes' classes.</p>


Well, metaclasses are what create these objects. They are the classes' classes.


In [161]:
%%HTML

<p style="color:red;font-size: 150%;">Everything, and I mean everything, is an object in Python. That includes ints, strings, functions and classes. All of them are objects. And all of them have been created from a class (which is also an object).</p>


Everything, and I mean everything, is an object in Python. That includes ints, strings, functions and classes. All of them are objects. And all of them have been created from a class (which is also an object).

Changing to blog post entitled Python 3 OOP Part 5—Metaclasses

object, which inherits from nothing.

reminds me of Eastern teachings of 'sunyata': emptiness, voidness, openness, nonexistence, thusness, etc.

>>> a = 5
>>> type(a)
<class 'int'>
>>> a.__class__
<class 'int'>
>>> a.__class__.__bases__
(<class 'object'>,)
>>> object.__bases__
()  # object, which inherits from nothing.
>>> type(a)
<class 'int'>
>>> type(int)
<class 'type'>
>>> type(float)
<class 'type'>
>>> type(dict)
<class 'type'>
>>> type(object)
<class 'type'>
>>> type.__bases__
(<class 'object'>,)

When you think you grasped the type/object matter read this and start thinking again

>>> type(type)
<class 'type'>

In [162]:
class MyType(type):
    pass

class MySpecialClass(metaclass=MyType):
    pass

In [163]:
msp = MySpecialClass()

In [164]:
type(msp)


Out[164]:
__main__.MySpecialClass

In [165]:
type(MySpecialClass)


Out[165]:
__main__.MyType

In [166]:
type(MyType)


Out[166]:
type

Metaclasses are a very advanced topic in Python, but they have many practical uses. For example, by means of a custom metaclass you may log any time a class is instanced, which can be important for applications that shall keep a low memory usage or have to monitor it.


In [167]:
%%HTML

<p style="color:red;font-size: 150%;">"Build a class"? This is a task for metaclasses. The following implementation comes from Python 3 Patterns, Recipes and Idioms.</p>


"Build a class"? This is a task for metaclasses. The following implementation comes from Python 3 Patterns, Recipes and Idioms.


In [168]:
class Singleton(type):
    instance = None
    def __call__(cls, *args, **kwargs):
        if not cls.instance:
            cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls.instance

In [169]:
class ASingleton(metaclass=Singleton):
    pass

a = ASingleton()
b = ASingleton()
print(a is b)


True

In [170]:
print(hex(id(a)))
print(hex(id(b)))


0x7fa9bbd939b0
0x7fa9bbd939b0

The constructor mechanism in Python is on the contrary very important, and it is implemented by two methods, instead of just one: new() and init().


In [171]:
%%HTML

<p style="color:red;font-size: 150%;">The tasks of the two methods are very clear and distinct: __new__() shall perform actions needed when creating a new instance while __init__ deals with object initialization.</p>


The tasks of the two methods are very clear and distinct: __new__() shall perform actions needed when creating a new instance while __init__ deals with object initialization.


In [172]:
class MyClass:
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        # do something here
        obj.one = 1
        return obj  # instance of the container class, so __init__ is called

In [173]:
%%HTML
<p style="color:red;font-size: 150%;"> Anyway, __init__() will be called only if you return an instance of the container class. </p>


Anyway, __init__() will be called only if you return an instance of the container class.


In [174]:
my_class = MyClass()

In [175]:
my_class.one


Out[175]:
1

In [176]:
class MyInt:
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        obj.join = ':'.join
        return obj

In [177]:
mi = MyInt()
print(mi.join(str(n) for n in range(10)))


0:1:2:3:4:5:6:7:8:9

Object creation is behaviour. For most classes it is enough to provide a different __init method, but for immutable classes one often have to provide a different \new__ method.

In this subsection, as preparation for enumerated integers, we will start to code a subclass of int that behave like bool. We will start with string representation, which is fairly easy.


In [178]:
class MyBool(int):
    def __repr__(self):
        return 'MyBool.' + ['False', 'True'][self]

In [179]:
t = MyBool(1)

In [180]:
t


Out[180]:
MyBool.True

In [181]:
bool(2) == 1


Out[181]:
True

In [182]:
MyBool(2) == 1


Out[182]:
False

In [183]:
%%HTML
<p style="color:red;font-size: 150%;">In many classes we use __init__ to mutate the newly constructed object, typically by storing or otherwise using the arguments to __init__. But we cant do this with a subclass of int (or any other immuatable) because they are immutable.</p>


In many classes we use __init__ to mutate the newly constructed object, typically by storing or otherwise using the arguments to __init__. But we can’t do this with a subclass of int (or any other immuatable) because they are immutable.

The solution to the problem is to use new. Here we will show that it works, and later we will explain elsewhere exactly what happens.


In [184]:
bool.__doc__


Out[184]:
'bool(x) -> bool\n\nReturns True when the argument x is true, False otherwise.\nThe builtins True and False are the only two instances of the class bool.\nThe class bool is a subclass of the class int, and cannot be subclassed.'

In [185]:
class NewBool(int):
    def __new__(cls, value):
        # bool 
        return int.__new__(cls, bool(value))

In [186]:
y = NewBool(56)
y == 1


Out[186]:
True