A class like this is useful although I've called it NamedObject (by analogy to NamedTuple). It's useful in contexts other than as a sentinel, for example to represent deleted values, but the fundamental aspects are that it's a unique object that has a name.
I've done two different things in my case:
(1) add __setattr__ so you can't set values on this object (to prevent someone that gets the object from operating on it as if it's some other object).
(2) printing str(descr) not repr(descr). Since the standard use case is passing a string to the constructor, it's not valuable for it to print quotes around it. I also use <> instead of NamedObject() but I don't care about that distinction.
>>> a = NamedObject('DELETED'), NamedObject('DELETED')
>>> a
(<DELETED>, <DELETED>)
>>> a[0] == a[1]
False
Note the last line: this is different from how unittest.mock.sentinel works. I find both cases useful (and particularly find the unittest.mock.sentinel version more useful in unittests).
Writing this up I suppose UniqueObject might be a better name for this than NamedObject.