I wrote a class in python which inherits from type . I thought that this was the only requirement for a class so as to be called as a metaclass but had not defined a __new__ method for it. But on instantiating with this new class as the metaclass I got an error stating the below :
TypeError: type.__new__() takes exactly 3 arguments (0 given)
The following is my code :
class a(type) :
pass
c= a()
Now when the class statement is being processed , that the __new__ method of type is being called is my assumption. This is because the default metaclass of all classes in python is type .
Now when I am instantiating the class a , which I have assumed to be a metaclass under the assumption that any class inheriting from (type) is a metaclass , isn't it the same as creating a class ? Why should this not result in type.__new__ being called with correct arguments ?
This does not work:
class a(type) :
pass
c = a()
...for the same reason for which this does not work:
c = type()
In the end, both do the same.
To use it as a metaclass, do this:
>>> class Class(metaclass=a):
... pass
...
>>> Class
<class '__main__.Class'>
>>> type(Class)
<class '__main__.a'>
You could also instantiate the class directly, as you tried, but you have to provide the correct arguments:
AnotherClass = type('AnotherClass', (), {})
YetAnotherClass = a('YetAnotherClass', (), {})
This error is due to you not respecting type's signature.
Inheriting from type is indeed enough for a class to be used as a metaclass, but the thing is you actually have to use it as a metaclass.
type itself has "two working modes: if called with 3 positional arguments, it creates a new class. And then type is the metaclass of that class. If called with 1 positional argument, it creates no new class or object at all - instead, it just returns that object's class.
But it makes no sense calling type with no arguments at all. And the arguments in the modes above are not optional. So, you will get a TypeError if your try to call type with no arguments at all - and that is not a "TypeError because something went wrong with the type class" - it is a "TypeError because your call did not match the callable signature".
When you inherit from type and change nothing, you class will behave exactly the same as the original type: you can call it with either one or three positional arguments, and the code responsible for working in either mode lies in type.__new__.
Now, if you want to use your class as a metaclass, you can indeed call it, but in the three argument form: you ass it the new class name, its bases and its attributes - which can actually be all empty, but you have to pass a string, a tuple and a dictionary as these three arguments:
class A(type): pass
myclass = A("", (), {})
And now, A is working as the metaclass for myclass:
In [16]: type(myclass)
Out[16]: __main__.A
However, whenever one defines a metaclass it is more usual to use it with the metaclass= named argument when declaring a class body:
In [17]: class MyOtherClass(metaclass=A):
...: pass
...:
In [18]: type(MyOtherClass)
Out[18]: __main__.A
Python's runtime will then compile this class body, and when the bytecod for it is executed, it will make the call to your metaclass' __new__ (and then __init__, and before that its __prepare__) method, so that it works as a metaclass.
So, just in case it is not clear: when you derive a class from type intending to use it as a metaclass, there is no need to further instantiate it to say that "it is now a metaclass". A subclass of type already can be a metaclass, and its instances will be classes, that will have it as a metaclass.
Related
Say I want to implement a metaclass that should serve as a class factory. But unlike the type constructor, which takes 3 arguments, my metaclass should be callable without any arguments:
Cls1 = MyMeta()
Cls2 = MyMeta()
...
For this purpose I defined a custom __new__ method with no parameters:
class MyMeta(type):
def __new__(cls):
return super().__new__(cls, 'MyCls', (), {})
But the problem is that python automatically calls the __init__ method with the same arguments as the __new__ method, so trying to call MyMeta() ends up throwing an exception:
TypeError: type.__init__() takes 1 or 3 arguments
Which makes sense, since type can be called with 1 or 3 arguments. But what's the correct way to fix this? I see 3 (4?) options:
I could add an empty __init__ method to my metaclass, but since I'm not sure if type.__init__ does anything important, this might not be a good idea.
I could implement an __init__ method that calls super().__init__(cls.__name__, cls.__bases__, vars(cls)).
I could use a meta-metaclass and override its __call__ method, rather than messing with __new__ and __init__.
Bonus option: Maybe I shouldn't try to change the signature?
So my question is: Are the 3 solutions I listed correct or are there any subtle bugs hidden in them? Which solution is best (i.e. the most correct)?
An interface deviating from the parent signature is a questionable design in regular classes too. You don't need the extra complexity of metaclasses to get into this kind of mess - you can cause the same new/init jumble by subclassing a datetime or whatever.
I want to have a metaclass and an easy way to create instances of that metaclass.
The usual pattern in Python is to write a factory using a from_something classmethod. To take the example of creating datetime instances from a different init signature, there is for example datetime.fromtimestamp, but you have many other examples too (dict.fromkeys, int.from_bytes, bytes.fromhex...)
There is nothing specific to metaclasses here, so use the same pattern:
class MyMeta(type):
#classmethod
def from_no_args(cls, name=None):
if name is None:
name = cls.__name__ + 'Instance'
return cls(name, (), {})
Usage:
>>> class A(metaclass=MyMeta):
... pass
...
>>> B = MyMeta.from_no_args()
>>> C = MyMeta.from_no_args(name='C')
>>> A.__name__
'A'
>>> B.__name__
'MyMetaInstance'
>>> C.__name__
'C'
I have a class object, cls. I want to know its metaclass. How do I do this?
(If I wanted to know its parent classes, I would do cls.__mro__. Is there something like this to get the metaclass?)
Ok - so, a class's metaclass is just its own "type", and can be given by
type(cls) and other means such as cls.__class__.
In Python 3.x there are no further ambiguities - as the syntax for creating a metaclass just passes it as a named parameter on the class declaration statement anyway.
However, the syntax used for creating a metaclass in Python 2.x generates a side-effect that is worth noting.
Upon doing
class A(object):
__metaclass__ = MyMeta
The __metaclass__ attribute is set to that value in the actual class, even if the actual metaclass is another one.
Consider:
def class_pre_decorator(name, bases, namespace):
# do something with namespace
return type(name, bases, namespace)
This is a callable that can be used in the metaclass declaration of both Python 2 and 3 - and it is valid. After resolving, the actual metaclass in both cases will simply be type. However, in Python 2.x, cls.__metaclass__ will point to the callable class_pre_decorator, even tough type(cls) returns type, which is the correct metaclass.(Note that using callables in this way, they will not be used agian when the class is further subclassed)
There is no way in Python 3 to guess the callable actually used to instantiate a class if it gives no other hint (like setting an attribute on the class) that it was used:
# python 2
class A(object):
__metaclass__ = class_pre_decorator
On the console:
In [8]: type(A)
Out[8]: type
In [9]: A.__metaclass__
Out[9]: <unbound method A.class_pre_decorator>
and
# Python 3
class A(metaclass=class_pre_decorator):
pass
And trying to read A.__metaclass__ will simply raise an AttributeError.
Take the following example script:
class A(object):
#classmethod
def one(cls):
print("I am class")
#staticmethod
def two():
print("I am static")
class B(object):
one = A.one
two = A.two
B.one()
B.two()
When I run this script with Python 2.7.11 I get:
I am class
Traceback (most recent call last):
File "test.py", line 17, in <module>
B.two()
TypeError: unbound method two() must be called with B instance as first argument (got nothing instead)
It appears that the #classmethod decorator is preserved across the classes, but #staticmethod is not.
Python 3.4 behaves as expected:
I am class
I am static
Why does Python2 not preserve #staticmethod, and is there a workaround?
edit: taking two out of a class (and retaining #staticmethod) seems to work, but this still seems strange to me.
classmethod and staticmethod are descriptors, and neither of them are doing what you expect, not just staticmethod.
When you access A.one, it's creating a bound method on A, then making that an attribute of B, but because it's bound to A, the cls argument will always be A, even if you call B.one (this is the case on both Python 2 and Python 3; it's wrong everywhere).
When you access A.two, it's returning the raw function object (the staticmethod descriptor doesn't need to do anything special aside from preventing binding that would pass self or cls, so it just returns what it wrapped). But that raw function object then gets attached to B as an unbound instance method, because without the staticmethod wrapping, it's just like you'd defined it normally.
The reason the latter works in Python 3 is that Python 3 has no concept of unbound methods. It has functions (which if accessed via an instance of a class become bound methods) and bound methods, where Python 2 has functions, unbound methods and bound methods.
Unbound methods check that they're called with an object of the correct type, thus your error. Plain functions just want the correct number of arguments.
The staticmethod decorator in Python 3 is still returning the raw function object, but in Python 3, that's fine; since it's not a special unbound method object, if you call it on the class itself, it's just like a namespaced function, not a method of any sort. You'd see the problem if you tried to do:
B().two()
though, because that will make a bound method out of that instance of B and the two function, passing an extra argument (self) that two does not accept. Basically, on Python 3, staticmethod is a convenience to let you call the function on instances without causing binding, but if you only ever call the function by referencing the class itself, it's not needed, because it's just a plain function, not the Python 2 "unbound method".
If you had some reason to perform this copy (normally, I'd suggest inheriting from A, but whatever), and you want to make sure you get the descriptor wrapped version of the function, not whatever the descriptor gives you when accessed on A, you'd bypass the descriptor protocol by directly accessing A's __dict__:
class B(object):
one = A.__dict__['one']
two = A.__dict__['two']
By directly copying from the class dictionary, the descriptor protocol magic is never invoked, and you get the staticmethod and classmethod wrapped versions of one and two.
DISCLAIMER: This is not really an answer, but it doesn't fit into a comment format either.
Note that with Python2 #classmethod is NOT correctly preserved across classes either. In the code below, the call to B.one() works as though it was invoked through class A:
$ cat test.py
class A(object):
#classmethod
def one(cls):
print("I am class", cls.__name__)
class A2(A):
pass
class B(object):
one = A.one
A.one()
A2.one()
B.one()
$ python2 test.py
('I am class', 'A')
('I am class', 'A2')
('I am class', 'A')
>>> type(type)
<type 'type'>
I expect the type of type to be a function since it's used to return the type of the argument. Does this mean a type can accept arguments? Is type something unusual/special?
type is a metaclass: a class whose instances are also classes. The type of any other builtin class will also be type - eg:
>>> type(object)
<class 'type'>
>>> type(list)
<class 'type'>
Indeed, every (new-style) class will also be an instance of type, although if it is defined with a custom metaclass, type(my_class) will be that metaclass. But since every metaclass is required to inherit from type, you will have, for any class:
>>> isinstance(my_class, type)
True
Classes are objects. All objects are instances of a class. So since a class is an object, it is an instance of some class. The class that a class is an instance of is named type. It is the base metaclass.
Originally type() was just a function that returned an object's class. In Python 2.2, user-defined classes and built-in types were unified, and type became a class. For backward compatibility, it still behaves like the old type() function when called with one argument.
type is Python's class ur-type. When called with a single argument it returns the type of the argument. When called with 3 arguments it returns a class whose characteristics are determined by the arguments.
As can be clearly seen in the documentations -
class type(object)
class type(name, bases, dict)
With one argument, return the type of an object. The return value is a type object. The isinstance() built-in function is recommended for testing the type of an object.
type is a class, not a function.
I can't understand why the following code behaves a particular way, which is described below:
from abc import ABCMeta
class PackageClass(object):
__metaclass__ = ABCMeta
class MyClass1(PackageClass):
pass
MyClass2 = type('MyClass2', (PackageClass, ), {})
print MyClass1
print MyClass2
>>> <class '__main__.MyClass1'>
>>> <class 'abc.MyClass2'>
Why does repr(MyClass2) says abc.MyClass2 (which is by the way not true)?
Thank you!
The problem stems from the fact that ABCMeta overrides __new__ and calls its superclass constructor (type()) there. type() derives the __module__ for the new class from its calling context1; in this case, the type call appears to come from the abc module. Hence, the new class has __module__ set to abc (since type() has no way of knowing that the actual class construction took place in __main__).
The easy way around is to just set __module__ yourself after creating the type:
MyClass2 = type('MyClass2', (PackageClass, ), {})
MyClass2.__module__ = __name__
I would also recommend filing a bug report.
Related: Base metaclass overriding __new__ generates classes with a wrong __module__, Weird inheritance with metaclasses
1: type is a type object defined in C. Its new method uses the current global __name__ as the __module__, unless it calls a metaclass constructor.