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>
In [133]:
%%HTML
<p style="color:red;font-size: 150%;">Yes, objects.</p>
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>
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>
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)
- 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)
- you can add attributes to it
In [139]:
print("ObjectCreator has an attribute 'new_attribute': ", hasattr(ObjectCreator, 'new_attribute'))
In [140]:
ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
print("ObjectCreator has an attribute 'new_attribute': ", hasattr(ObjectCreator, 'new_attribute'))
In [141]:
print("attribute 'new_attribute': ", ObjectCreator.new_attribute)
- 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))
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>
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_)
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))
In [148]:
print(type("1"))
In [149]:
print(type(int))
In [150]:
print(type(ObjectCreator))
In [151]:
print(type(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_)
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()])
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())
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>
In [159]:
%%HTML
<p style="color:red;font-size: 150%;">Metaclasses are the 'stuff' that creates classes.</p>
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>
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>
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]:
In [165]:
type(MySpecialClass)
Out[165]:
In [166]:
type(MyType)
Out[166]:
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>
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)
In [170]:
print(hex(id(a)))
print(hex(id(b)))
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>
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>
In [174]:
my_class = MyClass()
In [175]:
my_class.one
Out[175]:
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)))
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]:
In [181]:
bool(2) == 1
Out[181]:
In [182]:
MyBool(2) == 1
Out[182]:
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 can’t do this with a subclass of int (or any other immuatable) because they are immutable.</p>
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]:
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]: