I have to port a legacy code ( ~60K LOC) from Python 2 to 3 which has a couple of thousand of structures like below:
class Sample(object):
__slots__ = ('both', 'advertise')
class __metaclass__(type):
__instancecheck__ = classmethod(
lambda cls, inst: inst in ('both', 'advertise'))
both = 'both'
advertise = 'advertise'
This code works fine with Python 2 but doesn't compile with Python 3 and to resolve it I need to change it to
class Sample(object):
__slots__ = ('both', 'advertise')
class __metaclass__(type):
__instancecheck__ = classmethod(
lambda cls, inst: inst in ('both', 'advertise'))
def __init__(self):
both = 'both'
advertise = 'advertise'
What would be an efficient way to handle this change given that it has to be done over such a large file multiple times?
We have to consider that there may or may not be a __init__
function definition already for the class and there can be nested class definitions as well.
This is what I have tried so far.
2to3
doesn't recognize this as an issue and hence doesn't change it. ast
module to modify the parse tree in memory and then use unparse
to write back the modified code. But it is not straightforward. Can there be another quick and simple way to handle this change.
I don't know of any better way than to write a small script. I think the changes are small enough that you can get away with some nice heuristics and don't need the full power of ast
.
However, if you have this much repetition of code, I would remove the classes from your code altogether. You can replace them with either a code generator or write a factory function for these classes. This is going to future-proof your code for any changes.
Such a factory could look like this:
class HasStringInstances(type):
def __instancecheck__(cls, instance):
return instance in cls.__slots__
def create_special_class(*slots):
class SpecialClass(object, metaclass=HasStringInstances):
__slots__ = slots
def __init__(self):
for slot in self.__slots__:
# assign name as value
setattr(self, slot, slot)
return SpecialClass
Sample = create_special_class('both', 'advertise')