Calling the default code for metaclass's __init__ - python

So I have super(MyMeta, cls).__new__(cls, name, bases, attr) to call the default __new__ function? Can I do similar for __init__? To call the default __init__ function for a metaclass.
class MyMeta(type):
def __new__(cls, name, bases, attr): # real signature unknown
print("MyMeta.__new__ called")
return super(MyMeta, cls).__new__(cls, name, bases, attr)
def __init__(cls, name, bases, namespace, **kwargs):
print("MyMeta.__init__ called")
### how to call the default __init__()?

The correct way is super().__init__(name, bases, namespace, **kwargs) - super() will include the first argument (cls) automatically.
Also, there is no need to pass any parameters to super() (as there is no such need inside __new__): these are filled in automatically by the runtime.
When doing super().__new__(...) one have to explicitly include the first argument (the metaclass itself), because __new__ is a static method, and super does not add any parameters automatically.
For sake of completeness: the default metaclass implementation of __init__ (type.__init__) does nothing, and might just not be called at all. If you do include a __init__ method in a metaclass, though, it is important to call it with super() so that your metaclass can be used cooperatively with other metaclasses in case it is needed.

class MetaClass(type):
"""
Class is object. By default, "type" created the in memory class object.
Instance is object. Class created the in memory instance object.
"""
meta_counter = 0
def __new__(cls, name, bases, attr):
print("***MyMeta.__new__(): control class object creation")
cls_object = super(MetaClass, cls).__new__(cls, name, bases, attr)
return cls_object
def __init__(cls, name, bases, namespace, **kwargs):
print("***MyMeta.__init__() class-wide initializer")
cls.cls_order = MetaClass.meta_counter
MetaClass.meta_counter += 1
class AClass(metaclass=MetaClass):
def __init__(self):
print("AClass.__init__()")
class BClass(metaclass=MetaClass):
pass
class XClass:
def __new__(cls, name, *args, **kwargs):
print("ZClass.__new__(): constructor")
instance = super(XClass, cls).__new__(cls, *args, **kwargs)
return instance
def __init__(self, *args, **kwargs):
print("ZClass.__init__(): instance-wide initializer")
self.id = 1
print("\n\n---> ")
a = AClass()
print(a.cls_order)
b = BClass()
print(b.cls_order)
print(MetaClass.meta_counter)
print("\n\n***> ")
XClass('my-name')

Related

Decorate all function in a class by using metaclass

Lets say my class has many function, and I want to apply my decorator on each one of them. I have researched for a while, and find https://stackoverflow.com/a/6307917/18859252. By using metaclass, I can decorate all function in one line.
Here is my code (framework)
class myMetaClass(type):
def __new__(cls, name, bases, local):
for attr in local:
value = local[attr]
if callable(value) and attr != '__init__':
local[attr] = log_decorator(local['Variable'], value)
return super().__new__(cls, name, bases, local)
class log_decorator():
def __init__(self, Variable, func):
self.Variable = Variable
self.func = func
def __call__(self, *args, **kargs):
start_time = time.time()
self.func(*args, **kargs)
end_time = time.time()
class Test(metaclass = myMetaClass):
Variable = Some_Class
check_test = Some_Class
def __init__(self, **args):
self.connect = Some_Class(**args)
def A(self, a, b):
self.connect.abc
pass
then use like this
def Flow():
test = Test(**args)
test.A(a, b)
But here is problem, it show exception like:
TypeError:A() missing 1 required positional argument: 'self'
I have no idea about this problem. I'd be very grateful if anyone has an answer or if there is a better way.
The piece you are missing (and the bit I don't fully understand, but has to do with functions or methods as descriptors and how python will attach an instance as the self parameter) is that log_decorator() is an instance of that class and not a function or method (even though you have defined a __call__() method which makes it callable.)
Here is some code which just slightly changes the syntax needed, but gives you the results you want:
import functools
class log_decorator:
def __init__(self, Variable): # Note that the only parameter is Variable
self.Variable = Variable
def __call__(self, func):
#functools.wraps(func)
def decorated(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
return decorated
class myMetaClass(type):
def __new__(cls, name, bases, local):
for attr in local:
value = local[attr]
if callable(value) and attr != '__init__':
# Note the change in syntax vvv
local[attr] = log_decorator(local['Variable'])(value)
return super().__new__(cls, name, bases, local)

Wrap all methods of python superclass

Is there a way to wrap all methods of a superclass, if I can't change its code?
As a minimal working example, consider this base class Base, which has many methods that return a new instance of itself, and the descendent class Child
class Base:
def __init__(self, val):
self.val = val
def newinst_addseven(self):
return Base(self.val + 7)
def newinst_timestwo(self):
return Base(self.val * 2)
# ...
class Child(Base):
#property
def sqrt(self):
return math.sqrt(self.val)
The issue here is that calling childinstance.newinst_addseven() returns an instance of Base, instead of Child.
Is there a way to wrap the Base class's methods to force a return value of the type Child?
With something like this wrapper:
def force_child_i(result):
"""Turn Base instance into Child instance."""
if type(result) is Base:
return Child(result.val)
return result
def force_child_f(fun):
"""Turn from Base- to Child-instance-returning function."""
def wrapper(*args, **kwargs):
result = fun(*args, **kwargs)
return force_child_i(result)
return wrapper
Many thanks!
PS: What I currently do, is look at Base's source code and add the methods to Child directly, which is not very mainainable:
Child.newinst_addseven = force_child_f(Base.newinst_addseven)
Child.newinst_timestwo = force_child_f(Base.newinst_timestwo)
One option is to use a metaclass:
class ChildMeta(type):
def __new__(cls, name, bases, dct):
child = super().__new__(cls, name, bases, dct)
for base in bases:
for field_name, field in base.__dict__.items():
if callable(field):
setattr(child, field_name, force_child(field))
return child
class Child(Base, metaclass=ChildMeta):
pass
It will automatically wrap all the Bases methods with your force_child decorator.

How to dynamically generate an intermediate class with Python 3 metaclasses

See full gist here
Consider the case where we have a simple metaclass that generates the __init__ method for a class
class TestType(type):
def __new__(cls, cname, bases, attrs):
# Dynamically create the __init__ function
def init(self, message):
self.message = message
# Assign the created function as the __init__ method.
attrs['__init__'] = init
# Create the class.
return super().__new__(cls, cname, bases, attrs)
class Test(metaclass=TestType):
def get_message(self):
return self.message
Now this is all good and well to use
test = Test('hello')
assert test.get_message() == 'hello'
But we have problems when subclassing, because if you want to subclass the __init__ method what of course happens is the subclassed method just gets overwritten.
class SubTest(Test):
def __init__(self, first, second):
self.first = first
self.second = second
super().__init__(first + ' ' second)
subtest = SubTest('hello', 'there')
This will obviously give the
TypeError: init() takes 2 positional arguments but 3 were given
The only way I can think to solve this is to create an intermediate class in the __new__ method of the metaclass and make this the base for the class we are creating. But I can't get this to work, I tried something like this
class TestType(type):
def __new__(cls, cname, bases, attrs):
# Dynamically create the __init__ function
def init(self, message):
self.message = message
# If the __init__ method is being subclassed
if '__init__' in attrs:
# Store the subclass __init__
sub_init = attrs.pop('__init__')
# Assign the created function as the __init__ method.
attrs['__init__'] = init
# Create an intermediate class to become the base.
interm_base = type(cname + 'Intermediate', bases, attrs)
# Add the intermediate class as our base.
bases = (interm_base,)
# Assign the subclass __init__ as the __init__ method.
attrs['__init__'] = sub_init
else:
# Assign the created function as the __init__ method.
attrs['__init__'] = init
# Create the class.
return super().__new__(cls, cname, bases, attrs)
But this gives me recursion error
RecursionError: maximum recursion depth exceeded while calling a Python object
The infinite recursion is caused by the fact that the type constructor can return an instance of your metaclass.
In this line here:
interm_base = type(cname + 'Intermediate', bases, attrs)
If any of the base classes in bases is an instance of TestType, then the subclass will also be an instance of TestType. That is why Test can be created with no problems, but SubTest causes infinite recursion.
The fix is simple: Create the intermediate class without an __init__ attribute. That way if '__init__' in attrs: will be False, and the endless recursion is avoided.
class TestType(type):
def __new__(cls, cname, bases, attrs):
# Dynamically create the __init__ function
def init(self, message):
self.message = message
# If the __init__ method is being subclassed
if '__init__' in attrs:
# Create an intermediate class to become the base.
interm_base = type(cname + 'Intermediate', bases, {})
# Add the intermediate class as our base.
bases = (interm_base,)
else:
# Assign the created function as the __init__ method.
attrs['__init__'] = init
# Create the class.
return super().__new__(cls, cname, bases, attrs)

Python override metaclass set attribute

Suppose I have a class:
class ABC:
pass
And I do:
ABC.x = 123
This does not give me an error, even though I haven't created an object. Which of the __func__ methods is being called, and how do I override it?
You need to define a metaclass and override __setattr__ there, then make your class use that metaclass.
class Meta(type):
def __new__(meta, name, bases, attrs):
return super().__new__(meta, name, bases, attrs)
def __setattr__(cls, attr, value):
print("I am setting {}.{} = {}".format(cls.__name__, attr, value))
return super().__setattr__(attr, value)
class Foo(metaclass=Meta):
pass
Then:
>>> Foo.blah = 2
I am setting Foo.blah = 2
>>> Foo.blah
2

TypeError: type() takes 1 or 3 arguments

I have a TypeClass to make Class:
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
print('call __new__ from MyMetaClass.')
return type(cls.__name__, *args, **kwargs)
but when use it :
Foo= MyMetaClass('Foo', (), {'name':'pd'})
raise Error :
TypeError: type() takes 1 or 3 arguments
if Change it like :
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
print('call __new__ from MyMetaClass.')
return type(cls.__name__, (), {})
it will works okey !
where is problem ?
The __new__ method is passed 3 positional arguments in args; the class name, the baseclasses and the class body. The cls argument is bound to the metaclass, so MyMetaClass here.
You are adding another name to that sequence; drop the name, or remove the first argument from args:
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
print('call __new__ from MyMetaClass.')
return type(*args, **kwargs)
or
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
print('call __new__ from MyMetaClass.')
return type(cls.__name__, *args[1:], **kwargs)
The cls argument is the metaclass object however, so unless you want all your classes to be called MyMetaClass I'd stick with the first option.
See the Customizing class creation section of the Python data model:
These steps will have to be performed in the metaclass’s __new__() method – type.__new__() can then be called from this method to create a class with different properties. This example adds a new element to the class dictionary before creating the class:
class metacls(type):
def __new__(mcs, name, bases, dict):
dict['foo'] = 'metacls was here'
return type.__new__(mcs, name, bases, dict)
and the object.__new__ documentation:
__new__() is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression (the call to the class).
where class of which an instance was requested is your metaclass (producing a class object).
Demo:
>>> class MyMetaClass(type):
... def __new__(cls, *args, **kwargs):
... print('call __new__ from MyMetaClass.')
... return type(*args, **kwargs)
...
>>> class Foo(object):
... __metaclass__ = MyMetaClass
...
call __new__ from MyMetaClass.
>>> Foo
<class '__main__.Foo'>
>>> class MyMetaClass(type):
... def __new__(cls, *args, **kwargs):
... print('call __new__ from MyMetaClass.')
... return type(cls.__name__, *args[1:], **kwargs)
...
>>> class Foo(object):
... __metaclass__ = MyMetaClass
...
call __new__ from MyMetaClass.
>>> Foo
<class '__main__.MyMetaClass'>
>>> # Note the ^^^^^^^^^^^^ class.__name__ attribute here
...

Categories