This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Structure._anonymous_ should not allow strings
Type: Stage: resolved
Components: ctypes Versions: Python 3.2, Python 3.3, Python 2.7
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: amaury.forgeotdarc, meador.inge, ncoghlan, techtonik, terry.reedy, vstinner
Priority: normal Keywords:

Created on 2012-11-27 21:29 by techtonik, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (9)
msg176494 - (view) Author: anatoly techtonik (techtonik) Date: 2012-11-27 21:29
http://docs.python.org/2/library/ctypes.html#ctypes.Structure._anonymous_

  "An optional sequence that lists the names of unnamed (anonymous) fields".

If you feed it a string, such as _offset, it will print a very interesting error:

...
  File "C:\roundup\.\roundup\backends\portalocker.py", line 70, in <module>
    class A_OFFSET_UNION(Union):
AttributeError: type object '_OFFSET_UNION' has no attribute '_'

Considering complexity that ctypes already has, the simple check that _anonymous_ is a sequence (and not a string) will make our lifes easier.
msg176495 - (view) Author: anatoly techtonik (techtonik) Date: 2012-11-27 21:30
s/A_OFFSET_UNION/_OFFSET_UNION/
msg176496 - (view) Author: anatoly techtonik (techtonik) Date: 2012-11-27 21:31
The union definition for the curious:

    class _OFFSET(Structure):
        _fields_ = [
            ('Offset', DWORD),
            ('OffsetHigh', DWORD)]

    class _OFFSET_UNION(Union):
        _anonymous_ = '_offset'
        _fields_ = [
            ('_offset', _OFFSET),
            ('Pointer', PVOID)]
msg176497 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-11-27 22:01
> An optional sequence that lists the names of unnamed (anonymous) fields

Yes, it must be a sequence.

> _anonymous_ = '_offset'

This is a string, not a sequence. Just use _anonymous_ = ['_offset'].
msg176499 - (view) Author: anatoly techtonik (techtonik) Date: 2012-11-27 22:29
ctypes should throw proper exception if a string is used instead of sequence. It doesn't do this.
msg176501 - (view) Author: Meador Inge (meador.inge) * (Python committer) Date: 2012-11-28 01:02
A string *is* a sequence.  That is actually part of the problem.
Consider a slight variation on the original repro case:

>>> class _OFFSET(Structure):
...     _fields_ = [
...         ('Offset', c_int),
...         ('OffsetHigh', c_int)]
... 
[70412 refs]
>>> class _OFFSET_UNION(Union):
...     _anonymous_ = 12
...     _fields_ = [
...         ('_offset', _OFFSET),
...         ('Pointer', c_int)]
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: _anonymous_ must be a sequence

As expected, a TypeError is produced.

Now consider the original error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object '_OFFSET_UNION' has no attribute '_'

This happens because the string sequence '_offset' is iterated and the
first item in the iteration is '_', which isn't a field of _OFFSET_UNION.

So, the error checking is already there (in the form of PySequence_Fast)
and is consistent with the documentation.  This should be closed.
msg176505 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2012-11-28 01:32
Yes, if you pass "string" when you meant to pass ["string"], you will often get awful error messages.

This is one of the downsides of strings being iterable, but we're not going to add "if isinstance(obj, str): throw TypeError(msg)" special cases everywhere to address it.

It's definitely a wart in Python, but it's one Python developers just have to get used to (and learn to suspect whenever they see a single-character string in an error message).
msg176537 - (view) Author: anatoly techtonik (techtonik) Date: 2012-11-28 12:23
I agree with you on a generic case, where "Special cases aren't special enough to break the rules.", but this special case in ctypes and _anonymous_ context is where the rule "Although practicality beats purity." should apply. Otherwise I can't see any examples where the latter wins, and why it presents in the The Zen at all.
msg176540 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2012-11-28 12:37
OTOH, __slots__ also allows a single string, but it is silently converted to a 1-tuple:

class C:
    __slots__ = 'abc'
assert 'abc' in C.__dict__
History
Date User Action Args
2022-04-11 14:57:38adminsetgithub: 60770
2014-07-02 22:16:12terry.reedysetstatus: open -> closed
stage: resolved
2012-11-30 21:34:22terry.reedysetnosy: + terry.reedy

versions: - Python 3.1
2012-11-28 12:37:33amaury.forgeotdarcsetstatus: pending -> open
nosy: + amaury.forgeotdarc
messages: + msg176540

2012-11-28 12:23:43techtoniksetstatus: closed -> pending

messages: + msg176537
2012-11-28 01:32:05ncoghlansetstatus: open -> closed

nosy: + ncoghlan
messages: + msg176505

resolution: wont fix
2012-11-28 01:02:36meador.ingesetnosy: + meador.inge
messages: + msg176501
2012-11-27 22:29:31techtoniksetstatus: closed -> open
resolution: not a bug -> (no value)
messages: + msg176499
2012-11-27 22:01:03vstinnersetstatus: open -> closed

nosy: + vstinner
messages: + msg176497

resolution: not a bug
2012-11-27 21:31:33techtoniksetmessages: + msg176496
versions: + Python 3.1, Python 3.2, Python 3.3
2012-11-27 21:30:24techtoniksetmessages: + msg176495
2012-11-27 21:29:49techtonikcreate