Virtual subclasses are different in that there is no inheritance - the abstract base class registers virtual subclasses. This registration takes place outside of the class hierarchy, so its easy to replace virtual subclasses, or remove them entirely. This is all done with the ABCMeta class, part of the abc module.
Suppose I have a Shape class that can move to a specified point. Such an implementation might look like this.
class Shape(object):
def move(self, x, y):
print 'Moving to X: %s Y: %s'%(x, y)
if __name__ == '__main__':
my_shape = Shape()
my_point = (50, 100)
my_shape.move(*my_point)
The Shape.move() method is expecting a point in which to move. This is passed as a tuple, containing x, y coordinates. However, Shape is a legacy class that's been around forever and is being replaced with ShapeNew.
class ShapeNew(object):
def move(self, point):
print 'New Moving to X: %s Y: %s'%(point.x, point.y)
The ShapeNew.move() now expects a point object with x and y attributes, not a tuple with zero-based index lookups. This is where the ABCMeta class comes in handy.
from abc import ABCMeta
class Point(object):
__metaclass__ = ABCMeta
def __init__(self, *args):
self.data = args
def __getitem__(self, index):
return self.data[index]
def __len__(self):
return len(self.data)
def get_x(self):
return self[0]
def get_y(self):
return self[1]
x = property(get_x)
y = property(get_y)
Point.register(tuple)
We now have a Point class that we can use with the ShapeNew.move() method. You'll notice, Point sets its __metaclass__ attribute to ABCMeta. Following the definition of Point, we see Point.register(tuple). This tells the Python that tuple is a virtual subclass of Point. So Point can now be used anywhere tuples were used for the old Shape.move() method.
from abc import ABCMeta
class Point(object):
__metaclass__ = ABCMeta
def __init__(self, *args):
self.data = args
def __getitem__(self, index):
return self.data[index]
def __len__(self):
return len(self.data)
def get_x(self):
return self[0]
def get_y(self):
return self[1]
x = property(get_x)
y = property(get_y)
Point.register(tuple)
class Shape(object):
def move(self, x, y):
print 'Moving to X: %s Y: %s'%(x, y)
class ShapeNew(object):
def move(self, point):
print 'New Moving to X: %s Y: %s'%(point.x, point.y)
if __name__ == '__main__':
my_shape = Shape()
my_shape_new = ShapeNew()
my_point = Point(50, 100)
my_shape.move(*my_point)
my_shape_new.move(my_point)
Here, we're using ABCMeta to help us transition from legacy code to a newer implementation. So when Point no longer needs to be a tuple, we can remove ABCMeta as its __metaclass__ and remove the Point.register(tuple) resgistration.
The registration makes issubclass(tuple, Point) and isinstance((50, 100), Point) both return True, but your example does not make use of this. Your example still runs perfectly, if you remove ABCMeta as its __metaclass__ and remove the Point.register(tuple) registration without changing anything else.
ReplyDeleteGood point. Thanks for that.
ReplyDeleteWait, does Anonymous's comment above mean that you can accomplish this migration with ABCmeta?
ReplyDelete