Haydon Berrow
Haydon Berrow

Reputation: 495

Is this a valid use of metaclasses

I've been watching some videos on decorators and metaclasses and I think I understand them better now. One maxim I took away was "don't use metaclasses if you can do it more simply without using them". Some time ago I wrote a metaclass without really understanding what I was doing and I went back and reviewed it. I'm pretty certain that I've done something sensible here but I thought I'd check ....

PS I'm mildly concerned that the Colour class is used in the Metaclass definition, I feel it ought to be used at the Class level but that would complicate the code.

import webcolors

# This is a holding class for demo purposes
# actual code allows much more, eg 0.3*c0 + 0.7*c1
class Colour(list):
    def __init__(self,*arg):
        super().__init__(arg)

# define a metaclass to implement Class.name
class MetaColour(type):
    def __getattr__(cls,name):
        try:
            _ = webcolors.name_to_rgb(name)
            return Colour(_.blue,_.green,_.red)
        except ValueError as e:
            raise ValueError(f"{name} is not a valid name for a colour ({e})")
        return f"name({name})"

# a class based on the metaclass MetaColour
class Colours(metaclass=MetaColour):
    pass


print("blue = ",Colours.blue)
print("green = ",Colours.green)
print("lime = ",Colours.lime)
print("orange = ",Colours.orange)
print()
print("lilac = ",Colours.lilac)

Edit: I realise I could have written the Colour class so that Colour("red") was equivalent to Colours.red but felt at the time that using Colours.red was more elegant and added the implication that the Colour 'red' was a constant, not something that has to be looked up and can vary.

Upvotes: 1

Views: 58

Answers (1)

jsbueno
jsbueno

Reputation: 110811

If you really need Colours to be a class, then this metaclass just does it job - and seems fine. There is no problem at all in making use Colour inside it - there is no such thing as "metaclass code can not make use of any 'ordinary' class" - it is Python code as usuall.

The remark I'd do there is that maybe you don't need to use Colours as a class, and instead just create the Colours class, with all the functionality you need, and create a single instance of it. The remainder of the code will use this instance instead of the Colours class.

Yes, a single instance is the "singleton pattern" - but unlike some complicated code you can find around on how to make your class "be a singleton" (including some widely spread bad-practice about needing a metaclass to have a singleton in Python), you can just create the instance, assign it to a name, and be done with it. Just like in your example you have the "webcolors" object you are using.

For an extra-singleton bonus, you can make your single instance of Colours be named Colours, and shadow the class, preventing any accidental use of the class instead of the instance.

(And, although it might be obvious, for sake of completeness: in the "use Colours as an instance" case there is no need for this metaclass at all - the same __getattr__ method goes into the class body)

Of course, again, if you have uses for Colours as a class, there is no problem with this design.

Upvotes: 1

Related Questions