[Update]
To date (2019), Python has introduced data classes - combined with optional type annotations and static type parsers like mypy, I think this is a resolved problem.
In terms of performance, searching for attributes in Python is a bit more expensive than most computer languages, so I guess some libraries decided to avoid it for performance reasons.
[original answer]
IMHO this is a matter of taste. Some people like this style:
def searchsorted(a, v, side='left', sorter=None): ... assert side in ('left', 'right'), "Invalid side '{}'".format(side) ... numpy.searchsorted(a, v, side='right')
Yes, if you call searchsorted with side='foo' , you can get the AssertionError method later at runtime - but at least the error will be pretty easy to spot when viewing the trace.
While other people may prefer (for the benefits that you highlighted):
numpy.searchsorted(a, v, side=numpy.CONSTANTS.SIDE.RIGHT)
I prefer the former because I think that rarely used constants are not worth the confusion of the namespace. You may not agree, and people may join either side due to other problems.
If you really don't care, nothing stops you from defining your own "enumerations":
class SIDE(object): RIGHT = 'right' LEFT = 'left' numpy.searchsorted(a, v, side=SIDE.RIGHT)
I think it is not worth it, but again it is a matter of taste.
[Update]
Stefan made a fair remark:
As soon as it becomes necessary to change the meaning of such an enumeration, finding and replacing a string in many places is not my idea of ββfun :-)
I see how painful it can be in a language without named parameters - for example, you should find the string 'right' and get a lot of false positives. In Python, you can narrow your search to side='right' .
Of course, if you are dealing with an interface that already has a specific set of enumerations / constants (for example, an external C library), then yes, imitate existing conventions in all respects.