Add a extra function to a class
# Function to add
def extra(self, arg):
print("[From extra]: ", arg)
# Version 1: Helper Function
def extras(cls):
cls.extra = extra
class C1:
pass
extras(C1)
class C2:
pass
extras(C2)
# Version 2: Metaclass
class Extras(type):
def __init__(cls, clsname, superclasses, attributedict):
cls.extra = extra
class C1(metaclass=Extras):
pass
class C2(metaclass=Extras):
pass
class = type(classname, superclasses, attributeddict)
type defines a __call__
and runs the following methods
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(cls, classname, superclasses, attributedict)
The following two cells are equivalent
class A:
pass
class B(A):
data = 1
def meth(self, arg):
return self.data + arg
b = B()
b.meth(2)
3
B = type("B", (A,), {"data": 1, "meth": (lambda x, y: x.data + y)})
b = B()
b.meth(2)
3
Thus, to control the way classes are created and augment their behavior.
We have to specify that a user-defined class be created from a user-defined metaclass instead of the normal type class.
__metaclass__
class A(metaclass=Meta):
pass
class B(C, metaclass=Meta):
pass
class A(object):
__metaclass = Meta
class B(C, object):
__metaclass__ = Meta
Although classes derived from object explicityly is not a must in Python2, the __metaclass__
declaration makes the resulting class new-style.
Thus, it's suggested to explicitly declared
Replace the type with metaclass when creating class
class = Meta(clsname, superclasses, attributedict)
Because metaclass is a subclass of type, the __call__
is delegated
Meta.__new__(Meta, clsname, superclasses, attributedict)
Meta.__init__(cls, classname, superclasses, attributedict)
class Meta(type):
def __new__(meta, classname, supers, classdict):
print("Meta New: ", meta, classname, supers, classdict, sep="\n\t...")
return type.__new__(meta, classname, supers, classdict)
def __init__(cls, classname, supers, classdict):
print("Meta New: ", cls, classname, supers, classdict, sep="\n\t...")
class A:
pass
print("---Create class B---")
class B(A, metaclass=Meta):
data = 1
def __init__(self):
print("Create instance of B")
def meth(self, arg):
return self.data + arg
print("---Create instance b---")
b = B()
---Create class B--- Meta New: ...<class '__main__.Meta'> ...B ...(<class '__main__.A'>,) ...{'__module__': '__main__', '__qualname__': 'B', 'data': 1, '__init__': <function B.__init__ at 0x1044b0d90>, 'meth': <function B.meth at 0x1044b0d08>} Meta New: ...<class '__main__.B'> ...B ...(<class '__main__.A'>,) ...{'__module__': '__main__', '__qualname__': 'B', 'data': 1, '__init__': <function B.__init__ at 0x1044b0d90>, 'meth': <function B.meth at 0x1044b0d08>} ---Create instance b--- Create instance of B
Metaclasses need not really be classes, it can be any callable object that accepts the arguments passed and returns an object compatible with the intended class
def MetaFunc(clsname, supers, clsdict):
print("MetaFunc: ", clsname, supers, clsdict, sep="\n\t...")
return type(clsname, supers, clsdict)
class A:
pass
print("---Create class B---")
class B(A, metaclass=MetaFunc):
data = 1
def __init__(self):
print("Create instance of B")
def meth(self, arg):
return self.data + arg
print("---Create instance b---")
b = B()
---Create class B--- MetaFunc: ...B ...(<class '__main__.A'>,) ...{'__module__': '__main__', '__qualname__': 'B', 'data': 1, '__init__': <function B.__init__ at 0x1044b0598>, 'meth': <function B.meth at 0x1044b01e0>} ---Create instance b--- Create instance of B
# Definition
class Meta(type):
def __new__(meta, clsname, supers, clsdict):
print("Meta: ", clsname)
return type.__new__(meta, clsname, supers, clsdict)
def toast(self):
return "toast"
class Super(metaclass=Meta):
def spam(self):
return "spam"
class Sub(Super):
def eggs(self):
return "eggs"
Meta: Super Meta: Sub
# Instance
x = Sub()
x.eggs()
x.spam()
x.toast()
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-9-49ca63ad3241> in <module>() 4 x.eggs() 5 x.spam() ----> 6 x.toast() AttributeError: 'Sub' object has no attribute 'toast'
# Class
x = Sub()
Sub.eggs(x)
Sub.spam(x)
Sub.toast()
'toast'