Skip to content

reprlib (used by pytest) infers builtin types based on class __name__ #113570

Closed
@jpivarski

Description

@jpivarski

Bug report

Bug description:

Code like the following:

import reprlib

class array:
    def __repr__(self):
        return "not array.array!"

reprlib.repr(array())

raises

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jpivarski/mambaforge/lib/python3.10/reprlib.py", line 52, in repr
    return self.repr1(x, self.maxlevel)
  File "/home/jpivarski/mambaforge/lib/python3.10/reprlib.py", line 60, in repr1
    return getattr(self, 'repr_' + typename)(x, level)
  File "/home/jpivarski/mambaforge/lib/python3.10/reprlib.py", line 86, in repr_array
    header = "array('%s', [" % x.typecode
AttributeError: 'array' object has no attribute 'typecode'

because reprlib uses type(x).__name__ to infer that x has type array.array, rather than my user-defined class:

cpython/Lib/reprlib.py

Lines 62 to 70 in cf34b77

def repr1(self, x, level):
typename = type(x).__name__
if ' ' in typename:
parts = typename.split()
typename = '_'.join(parts)
if hasattr(self, 'repr_' + typename):
return getattr(self, 'repr_' + typename)(x, level)
else:
return self.repr_instance(x, level)

Perhaps there's good reason to check the __name__ string instead of isinstance(x, array.array), to avoid unnecessarily importing the array module for start-up time or for minimizing clutter in the sys.modules. However, this test should check both the __name__ string and the __module__ string.

This affects any user-defined classes with the following names:

  • tuple
  • list
  • array
  • set
  • frozenset
  • deque
  • dict
  • str
  • int
  • instance

Some of these, admittedly, would be bad names for user-defined classes, but others are more reasonable, such as array and deque.1

Since these methods are publicly available on class reprlib.Repr, the method names can't change, but the lookup could be replaced using a dict like

class Repr:
    _lookup = {
        ("builtins", "tuple"): repr_tuple,
        ("builtins", "list"): repr_list,
        ("array", "array"): repr_array,
        ("builtins", "set"): repr_set,
        ("builtins", "frozenset"): repr_frozenset,
        ("collections", "deque"): repr_deque,
        ("builtins", "dict"): repr_dict,
        ("builtins", "str"): repr_str,
        ("builtins", "int"): repr_int,
    }

I encountered this in pytest. My error output contained

x          = <[ValueError('the truth value of an array whose length is not 1 is ambiguous; use ak.any() or ak.all()') raised in repr()] array object at 0x779b54763700>
y          = array([1, 2, 3])

or

x          = <[AttributeError("'array' object has no attribute 'typecode'") raised in repr()] array object at 0x728278bacd60>
y          = array([1, 2, 3])

for reasons that had nothing to do with the actual error, and the array.__repr__ code itself is error-free. (pytest overrides reprlib to provide a SafeRepr.)

Thanks!

CPython versions tested on:

3.10

Operating systems tested on:

Linux

Linked PRs

Footnotes

  1. In my case, I want the ragged library to provide a ragged.array because it reads like English that way. I also don't want to change the __name__ of my class to differ from its actual name. In particular, the Array API specification uses "array" as a type name.

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixes3.13bugs and security fixes3.14bugs and security fixestype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions