Skip to content

Commit bb6d539

Browse files
committed
New implementation of with_metaclass
1 parent 15e3143 commit bb6d539

File tree

1 file changed

+70
-12
lines changed

1 file changed

+70
-12
lines changed

six.py

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -816,20 +816,78 @@ def wrapper(f):
816816
wraps = functools.wraps
817817

818818

819+
class _WithMetaclass(type):
820+
"""
821+
Metaclasses to be used in six.with_metaclass: instances of
822+
_WithMetaclass are metaclasses, so this is a metametaclass.
823+
"""
824+
@classmethod
825+
def get(cls, meta, bases):
826+
"""
827+
Return a metaclass (an instance of _WithMetaclass) which, if an
828+
instance of it is used as base class, will create a class with
829+
metaclass "meta" and bases "bases".
830+
"""
831+
# We use "meta" as base class instead of "type" to support the
832+
# following wrong usage:
833+
#
834+
# class X(six.with_metaclass(M)):
835+
# __metaclass__ = M
836+
#
837+
return cls("metaclass", (meta,), dict(meta=meta, bases=bases))
838+
839+
def instance(self):
840+
"""
841+
Return an instance of the metaclass "self".
842+
"""
843+
return self.__new__(self, "temporary_class", (object,), {})
844+
845+
@property
846+
def __prepare__(self):
847+
# We forward __prepare__ to __prepare which is the actual
848+
# implementation.
849+
#
850+
# This is needed because an ordinary __prepare__ method on the
851+
# metametaclass would not be called, since it is overridden by
852+
# type.__prepare__ (as an application of the general principle
853+
# that instance attributes override type attributes). A property
854+
# works because it bypasses attribute lookup on the instance.
855+
return self.__prepare
856+
857+
def __prepare(self, name, __bases, **kwargs):
858+
"""
859+
Ensure that metaclass.__prepare__ is called with the correct
860+
arguments.
861+
"""
862+
return self.meta.__prepare__(name, self.bases, **kwargs)
863+
864+
def __call__(self, name, __bases, d):
865+
"""
866+
Create the eventual class with metaclass "self.meta" and bases
867+
"self.bases".
868+
"""
869+
return self.meta(name, self.bases, d)
870+
871+
819872
def with_metaclass(meta, *bases):
820873
"""Create a base class with a metaclass."""
821-
# This requires a bit of explanation: the basic idea is to make a dummy
822-
# metaclass for one level of class instantiation that replaces itself with
823-
# the actual metaclass.
824-
class metaclass(type):
825-
826-
def __new__(cls, name, this_bases, d):
827-
return meta(name, bases, d)
828-
829-
@classmethod
830-
def __prepare__(cls, name, this_bases):
831-
return meta.__prepare__(name, bases)
832-
return type.__new__(metaclass, 'temporary_class', (), {})
874+
# This requires a bit of explanation: with_metaclass() returns
875+
# a temporary class which will setup the correct metaclass when
876+
# this temporary class is used as base class.
877+
#
878+
# In detail: let T = with_metaclass(meta, *bases). When the user
879+
# does "class X(with_metaclass(meta, *bases))", Python will first
880+
# determine the metaclass of X from its bases. In our case, there is
881+
# a single base class T. Therefore, the metaclass will be type(T).
882+
#
883+
# Next, Python will call type(T)("X", (T,), methods) and it is this
884+
# call that we want to override. So we need to define a __call__
885+
# method in the metaclass of type(T), which needs a metametaclass,
886+
# called "_WithMetaclass".
887+
# The metaclass type(T) is returned by _WithMetaclass.get(...) and
888+
# the instance() method creates an instance of this metaclass, which
889+
# is a regular class.
890+
return _WithMetaclass.get(meta, bases).instance()
833891

834892

835893
def add_metaclass(metaclass):

0 commit comments

Comments
 (0)