Francis Cagney
Francis Cagney

Reputation: 326

Python metaclass grammar

What do you 'do' with a metaclass?

What is the verb when you apply a metaclass

class Demo(metaclass=MetaclassRoot):
    """Will fail because MetaclassRoot can only be applied to MetaclassBase"""

This 'raises' an exception.

SEE there's a verb I have no problems with associated with the noun Exception.

You raise and catch exceptions. You decorate with decorators. You iterate with iterators. You inherit (from) and instantiate classes.

What do you do with a metaclass?

I know this leads into another discussion: Are metaclasses searching for a problem? I actually have an opinion on that.

But I'm serious. I need an official, or the obviously best verb for when you use a metaclass to create a new class with type() == metaclass

I've got two exceptions (actually 8)

class MetaclassIsroot(MetaclassFault):
    format_cause = "<metaclass> '{}': Only {} can apply 'MetaclassRoot'"

class BaseIsAbstract(MetaclassFault):
    format_cause = "'MetaclassBase' is abstract and cannot be applied to '{}'"

Is apply the best verb for metaclass?

Upvotes: 0

Views: 97

Answers (2)

jsbueno
jsbueno

Reputation: 110476

Metaclass is just another kind of classes. So, if you say that you "instantiate a class" you can use just the same verb to a metaclass. The result of instantiating a metaclass is a (usually non-meta) class.

Maybe the verb "create" is, if less strictly correct, able to give a better idea of what is going on: you "create a class with a metaclass"

As for "are metaclasses in search of a problem": that is offtopic. The language needed then internally, and it had the nicety of exposing it to be used in regular Python code. No one "needs" to use metaclasses, and since Python 3.6, with the __init_subclass__, __set_name__ special methods, and the attributes in a class ordered by default, most uses of metaclasses were made unnecessary.

Upvotes: 2

Francis Cagney
Francis Cagney

Reputation: 326

I'm not answering my grammar question but the question: Are metaclasses searching for a problem?

It's hard to find a killer application for metaclasses if you only consider them in the context of class creation and instantiation.

Which pretty well every example I've seen focuses exclusively on.

You define a class to describe the behaviour of instances of your class.

You want a context manager to catch specific exceptions. You write a class with __enter__, and __exit__ methods.

You can't use the class as a context manager. But must instance the class and use its instance e.g.

with Catcher(keyError):
    return Commands[key]
raise InvalidCommand(key)

If you're declaring your own exceptions, why not make them context managers?

So another bit of code could write:

with InvalidCommand():
    <do things that might raise InvalidCommand>

That won't work. InvalidCommand expects one argument. Or 3 and lots of kwargs

Wouldn't it be nice if you could use the exception class as a context manager?

You can:

class CatcherMeta(type, AbstractCatcher):
    """Metaclass to allow exceptions to be used as catchers"""
    def __init__(cls, name, bases, clsdict):
        """Called everytime CatcherMeta used to create a class"""
        super().__init__(name, bases, clsdict)
        cls.caught = None 
        cls.tocatch = cls   # instance.tocatch is always the class
    
    
class Fault(AbstractCatcher, metaclass=CatcherMeta):
    """Generic Exception base class. Note not descendant of Exception"""
    ...

class MetaclassFault(Fault, TypeError):
    """Ancestor of all meta class exceptions"""

Now we can write either:

with MetaclassFault as fault:

or even

with MetaclassFault:
    return <something might raise MetaclassFault descendant>
with MetaclassFault.caught:
    return <something might raise type(caught) descendant>

MetaclassFault is a class but MetaclassFault.caught is an instance. They can both be used as context managers because AbstractCatcher defines the behaviors of both the instance (through inheritance, and the class through metaclass.

AbstractCatcher has no __init__ and expects .tocatch to be a subclass of Exception.

class AbstractCatcher:
    """Abstract exception catcher to be inherited..."""

    def iscaught(instance):
        """reports if the last with statement caught an exception"""
        return instance.caught is not None
        
    def __enter__(instance):
        """Initialise caught, return catchhook to 'as'"""
        def catchhook():
            """Recreate caught to flatten call stack"""
            extype = type(instance.caught)
            return extype(*instance.caught.args)
    
        instance.caught = None
        return catchhook
    
    def __exit__(instance, extype, ex, tb):
        """If extype inherits from this exception..."""

        if extype is not None:  # instance.tocatch is abstract
            if issubclass(extype, instance.tocatch):
                instance.caught = ex
                return True     

Upvotes: 1

Related Questions