Reputation: 31
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
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