Skip to content

Commit 6b428b8

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

File tree

1 file changed

+73
-12
lines changed

1 file changed

+73
-12
lines changed

six.py

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -816,20 +816,81 @@ 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+
if "__metaclass__" in d:
870+
from warnings import warn
871+
warn("when using six.with_metaclass, remove __metaclass__ from your class", DeprecationWarning)
872+
return self.meta(name, self.bases, d)
873+
874+
819875
def with_metaclass(meta, *bases):
820876
"""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', (), {})
877+
# This requires a bit of explanation: with_metaclass() returns
878+
# a temporary class which will setup the correct metaclass when
879+
# this temporary class is used as base class.
880+
#
881+
# In detail: let T = with_metaclass(meta, *bases). When the user
882+
# does "class X(with_metaclass(meta, *bases))", Python will first
883+
# determine the metaclass of X from its bases. In our case, there is
884+
# a single base class T. Therefore, the metaclass will be type(T).
885+
#
886+
# Next, Python will call type(T)("X", (T,), methods) and it is this
887+
# call that we want to override. So we need to define a __call__
888+
# method in the metaclass of type(T), which needs a metametaclass,
889+
# called "_WithMetaclass".
890+
# The metaclass type(T) is returned by _WithMetaclass.get(...) and
891+
# the instance() method creates an instance of this metaclass, which
892+
# is a regular class.
893+
return _WithMetaclass.get(meta, bases).instance()
833894

834895

835896
def add_metaclass(metaclass):

0 commit comments

Comments
 (0)