WatchdogReset
WatchdogReset

Reputation: 31

How to replace __prepare__ for metaclass in python 2

I need to convert some code from python 3 to python 2. I have a metaclass where the __prepare__ method sets up a function in the class dict. I tried to translate to a __new__ method but I am unable to set up the SET_DEFAULTS function. Is that possible ?

I have a NameError: name 'SET_DEFAULTS' at initialization

class UazeMessageMeta (type):

    @staticmethod
    def __prepare__(name, bases, **kwargs):
        d = {}
        for b in bases: 
            if 'DEFAULT_VALUES' in dir(b):
                d.update(b.DEFAULT_VALUES)
        return { 
            'SET_DEFAULTS' : lambda **kwargs : d.update(kwargs),
            'DEFAULT_VALUES' : d
        }

class UazeMessage (bytearray):
    """
    A basic message (header only).  This class also provides the
    base behavior for all messages.
    """
    # gp test py27 -----------------
    __metaclass__ = UazeMessageMeta 

    # ------------

    priority    = MessageField(0,  1,  Priority)
    sequence    = MessageField(1,  7,  FieldType.UNSIGNED)
    readWrite   = MessageField(8,  1,  ReadWriteFlag)
    ack         = MessageField(9,  2,  Ack)
    channel     = MessageField(11, 2,  FieldType.UNSIGNED)
    category    = MessageField(13, 3,  Category)
    item        = MessageField(16, 8,  FieldType.UNSIGNED)

    DEFAULT_SIZE = 3

    def __init__(self, init=0, setdefaults=None, **kwargs):
        # If init is still or None, initialize the size of the message
        # using the default size provided in the class.
        if init == None or init == 0: 
            init = type(self).DEFAULT_SIZE
        super(UrmpMessage,self).__init__(init)
        # Set any default or provided fields.
        initval = {}
        if (isinstance(init, int) and setdefaults != False) or \
           (setdefaults == True):
            initval = dict(self.DEFAULT_VALUES)
        initval.update(kwargs)
        for key, value in initval.items():
            setattr(self, key, value)

class ResetBase (UazeMessage):
    """Reset response/request structure."""

    resetType = MessageField(24, 8, ResetType)

    SET_DEFAULTS(
        category  = Category.OPERATION,
        resetType = ResetType.SOFT,
        item      = 0)

    DEFAULT_SIZE = 4

Upvotes: 1

Views: 828

Answers (1)

jsbueno
jsbueno

Reputation: 110271

Ordinarily, you can't do that. The introduction of __prepare__ is a fundamental change in Python3, which allows the customization of the namespace where a class body itself is parsed.

I believe the main motivation doing that was to provide a way to replace the local namespace inside a class body with an OrderedDict, so that the class initialization (in the metaclass __new__ or __init__ methods) could benefit from the declaration order of methods and attributes inside the class body. It should be considered that as of Python 3.6 (final version due this week), an ordered dictionary is used by default in the class body, and a metaclass is no longer necessary for that.

The __prepare__ mechanism is way more flexible than that, and in a simpler use, allow one to simply pre-populate the class body dictionary with predetermined values. That is what your project do.

However, since this code does not need an special dictionary class, and just pre-populate an ordinary dictionary, all you need to do is to write an ordinary function that takes in a dictionary and base classes as parameters, and fills in that dictionary according to the existing code in the __prepare__ method. Then, call that function in the beggining of a class body, passing in the dictionary returned by the locals() call as parameter. That is it: the class body namespace can be pre-filled in the sameway.

def prepare(bases, dct):
    for base in bases:
        dct["special_attribute"] = {}
        if "special_attribute" in base.__dict__:
            dct["special_attribute" ].update(base.__dict__["special_attribute"])
     ...

class MyClass(bytearray):
    prepare((bytearray,), locals())
    ...

All that said, I really advise you to try if possible NOT to backport a project to Python2 at this point in time - it will just complicate your codebase - and give up using new features in a consistent way (for example, this tip above instead of __prepare__ )

Upvotes: 3

Related Questions