Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Portable meta class between python2 and python3

I'm trying to get a python2 program working in python3, it has the following Meta class definition. Which works just fine on Py2. What's the "best" way to have this be compatible with both py2 and py3?

It's failing in the unit test where it does:

try:
    raise Actor.DoesNotExist
except Actor.DoesNotExist:
    pass

Failure is:

AttributeError: type object 'Actor' has no attribute 'DoesNotExist'

The base meta class definition is:

class MetaDocument(type):
    def __new__(meta,name,bases,dct):

        class DoesNotExist(BaseException):
            pass

        class MultipleDocumentsReturned(BaseException):
            pass
        dct['DoesNotExist'] = DoesNotExist
        dct['MultipleDocumentsReturned'] = MultipleDocumentsReturned
        class_type = type.__new__(meta, name, bases, dct)
        if not class_type in document_classes:
            if name == 'Document' and bases == (object,):
                pass
            else:
                document_classes.append(class_type)
        return class_type

class Document(object):
    __metaclass__ = MetaDocument
like image 897
koblas Avatar asked Mar 14 '14 15:03

koblas


1 Answers

You could use the MetaDocument() metaclass as a factory to produce a class replacing your Document class, re-using the class attributes:

class Document(object):
    # various and sundry methods and attributes

body = vars(Document).copy()
body.pop('__dict__', None)
body.pop('__weakref__', None)

Document = MetaDocument(Document.__name__, Document.__bases__, body)

This doesn't require you to build the 3rd argument, the class body, manually.

You can turn this into a class decorator:

def with_metaclass(mcls):
    def decorator(cls):
        body = vars(cls).copy()
        # clean out class body
        body.pop('__dict__', None)
        body.pop('__weakref__', None)
        return mcls(cls.__name__, cls.__bases__, body)
    return decorator

then use as:

@with_metaclass(MetaDocument)
class Document(object):
    # various and sundry methods and attributes

Alternatively, use the six library for this:

@six.add_metaclass(MetaDocument)
class Document(object):

where the @six.add_metaclass() decorator also takes care of any __slots__ you may have defined; my simpler version above doesn't.

six also has a six.with_metaclass() base-class factory:

class Document(six.with_metaclass(MetaDocument)):

which injects an extra base class into the MRO.

like image 133
Martijn Pieters Avatar answered Oct 31 '22 15:10

Martijn Pieters