Handling objects and arrays simply in this tagging library
The solution is to have contents always be a list. If we don't want to impose this onto the caller of the constructor, we have to use a variable number of arguments:

def __init__(self, name, *contents, **attrs):
   ...


Now the functions can be called as table(tr(td('d1'), td('d2')), tr()) or more generally:

foo("name", child1, child2, ... attr1=value, attr2=value, ...)


Then inside the function, contents will be a tuple and attrs a dictionary.



Otherwise, I find your inappropriate usage of OOP unnecessary. For example, the class n does not really add value, and violates the Liskov Substitution Principle – the constructor signature is incompatible with the parent class. Instead, use closures to provide a default argument:

def namedtag(name):
    return lambda *contents, **kws: tag(name, *contents, **kws)