I think I might have a fundamental misunderstanding of what a python attribute actually is. Consider the following:
>>> class Test:
... pass
...
>>> t = Test()
>>> setattr(t, '0', 0)
>>> t.0
File "<stdin>", line 1
t.0
^
SyntaxError: invalid syntax
>>> getattr(t, '0')
0
>>> setattr(t, 'one', 1)
>>> t.one
1
>>> getattr(t, 'one')
1
Why does Python allow me to set an attribute if I can't legally access it with dot notation? I understand that t.0
makes no sense, but at the same time I wonder why it's no different than t.one
because I created them the same way.
This is just a quirk of the syntax and semantics of python. Any string can be used as an attribute name, however only identifiers can be used with dot notation. Thus the only way of accessing non-identifier attributes is with getattr/setattr or some other indirect function. Strangely enough this practice doesn't extend so far as to allow any type to be an attribute, only strings get that privilege.
Attributes are a kind of members any Python object can have. Usually you would expect the built-in syntax to dictate what kind of attribute names are accepted. For that, the definition is pretty clear:
attributeref ::= primary "." identifier
So what follows after the dot is required to be a valid identifier which limits the allowed attribute names easily. Ignoring other Unicode areas for now, it essentially means that the attribute may not start with a number. So 0
is not a valid identifier and as such t.0
is not a valid attribute reference as per the specification.
However, getattr
and alike work a bit differently. They just require the attribute name to be a string. And that string is passed on directly to the internal PyObject_GetAttr
functions. And those don’t require a valid identifier.
So using getattr
etc., you can essentially trick Python and attach attribute to objects, which names would not be allowed according to the specification of the language.