Reputation: 462
Specifically, is it acceptable/good practice to use a class attribute to create more class attributes, and then remove the original?
Say I have a superclass. From that, I derive many subclasses, but all of these need some class attributes defined. If I were to do this normally, it would look something like:
class Bar(Foo):
someval = 0
someotherval = ('a', 'b', 'c')
importantval = [1, 2, 3]
thisval = 'this'
names = ['John', 'Joe']
Every subclass would need to define all of these attributes. However, if we somehow use one variable to create all of these it would look something like:
class Bar(Foo):
classvars = (0, ('a', 'b', 'c'), [1, 2, 3], 'this', ['John', 'Joe'])
Then, after the class is created, it would internally look the same as the version of the class where we define all of the attributes separately.
See, I tried to do this before by using just a superclass without a metaclass with something like:
class Foo:
@classmethod
def __init__(cls, val):
cls.val = val
class Bar(Foo):
Foo.__init__(5)
But if you know how @classmethod
works, then you know that cls
ends up being a class Foo
reference, instead of a class Bar
reference (when called during Bar
's creation, and cls
only ends up being a Bar
reference during a Bar
instance creation). I then also tried to use @staticmethod
instead, but, to my knowledge, when creating a class, you cannot reference to the class being created outside of a method definition.
i.e.
class Bar(Foo):
Foo.__init__(Bar, 5)
That would raise a NameError (if Foo
's __init__
method were a static method).
Eventually, I learned about metaclasses, and I figured out that I could do this:
class MetaFoo(type):
def __init__(cls, name, bases, namespace):
super(MetaFoo, cls).__init__(name, bases, namespace)
if '_superclass' not in namespace: # A special attribute to distinguish between classes that we want to affect and those that we don't
attrnames = ('someval', 'someotherval', 'importantval', 'thisval', 'names')
if 'classvars' in namespace:
for attr, attrname in zip(getattr(cls, 'classvars'), attrnames):
setattr(cls, attrname, attr)
namespace[attrname] = attr
delattr(cls, 'classvars')
else: # Allow the definitions to be separate, instead of through "classvars", but also make sure that all the required attributes are defined
for attrname in attrnames:
if attrname not in namespace:
raise AttributeError('"%s" not found.' % attrname)
class Foo(metaclass=MetaFoo):
_superclass = True # The value of this attribute doesn't matter
def __init__(self, value):
self.value = value
def dosomething(self):
return self.value * self.someval # Can use type(self).someval for the class attribute, as long as the class itself is never passed as self
class Bar(Foo):
classvars = (5, ('c', 'b', 'a'), [3, 2, 1], 'This', ['Jimmy', 'Bob'])
What the above does, in short, is if it finds a class attribute called classvars
, it uses that to create other class attributes, then it removes classvars
. In this example, doing:
class Bar(Foo):
classvars = (5, ('c', 'b', 'a'), [3, 2, 1], 'This', ['Jimmy', 'Bob'])
gives you an identical result to:
class Bar(Foo):
someval = 5
someotherval = ('c', 'b', 'a')
importantval = [3, 2, 1]
thisval = 'This'
names = ['Jimmy', 'Bob']
My main question is: is it acceptable (or is it generally avoided for a some reason) to use a metaclass in this way, especially if you are already using a metaclass for a good reason?
A side question is: is there any way to accomplish this without a metaclass?
Upvotes: 2
Views: 469
Reputation: 110811
Yes, you could do that. But I don't know if it is simply better than copying+pasting all the needed attributes around.
If you will do this, to keep a minimum of readability for one looking at the code of one of your subclasses - and that includes you one week from now - you should at least use a dictionary instead of a sequence. That way you will always know which value maps to each attribute when looking at a subclass.
Also, have in mind that if at least some of the attributes have good default values that would be valid for a number of the subclasses, the inheritance mechanism is great in keeping these defaults while allowing you to explicitly declare only the attributes you really need to be different for each subclass (and typing less cruft than using a dictionary, which will require at a minimum extra quotes around the names).
Otherwise, as far as the language is concerned, that is ok. You'd rather have an special attribute name on a base class allowing you to enter the required/desired attributes names on the base class body, instead of having them hardcoded on the metaclass: that looks really "uncivilized". By having an attribute on the baseclass, the metaclass can be reusable for different object hierarchies.
(just use a for
iterating on the bases
parameter to the metaclass to check if the parameter with attribute names is present there)
Ah, as it is part of the question: about removing class attributes that won't be useful after class creation, using the metaclass - that is perfectly ok.
Upvotes: 3