Skip to content

Documentation from inspect.getdoc() for non-callable attributes math.e can be misleading. #135316

Closed as duplicate

Description

@astrolamb-gaming

Bug report

Bug description:

inspect module returns wrong documentation for math.e.

print(inspect.getdoc(math.e))
# Returns: 
# "Convert a string or number to a floating point number, if possible."

This should return something to the effect of:
"""
Returns the nearest representable value to the mathematical constant e, which has a value of 2.718281..." and is the base of natural logorithms.
"""

CPython versions tested on:

3.11

Operating systems tested on:

Windows

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Jun 9, 2025
  2. zware commented on Jun 9, 2025

    @zware
    Member

    math.e is just a float instance; inspect.getdoc can only return data about the float type from it. I'm afraid there's nothing we can do here.

  3. added
    pendingThe issue will be closed if no feedback is provided
    on Jun 9, 2025
  4. ZeroIntensity commented on Jun 10, 2025

    @ZeroIntensity
    Member

    In theory, we could create a separate float subclass for each constant (e, inf, etc.) that provides a specific docstring. But I don't think it's worth it, nor worth encouraging.

  5. removed
    pendingThe issue will be closed if no feedback is provided
    on Jun 10, 2025
  6. changed the title [-]documentation from inspect.getdoc() for math.e is incorrect[/-] [+]Documentation from inspect.getdoc() for non-callable attributes math.e can be misleading.[/+] on Jun 10, 2025
  7. terryjreedy commented on Jun 10, 2025

    @terryjreedy
    Member

    The issue with getdoc() is essentially the same as for help(). Only modules, classes, and functions have individual docstrings. dir(math.e) equals dir(float), with equal __doc__s, and the same is true for every float.

  8. astrolamb-gaming commented on Jun 10, 2025

    @astrolamb-gaming
    Author

    Admittedly while I've been working on the project that triggered this, I keep forgetting that math.e isn't a function. The explanations given make sense in that context. I do wonder, though, if it would be worth using a function that returns e, or tau, or pi, or whatever other constants, instead of just having the constant. Would there be unnecessary performance impact? I don't know, but in my opinion, it would be helpful to have a useful docstring about these constants.

  9. ZeroIntensity commented on Jun 10, 2025

    @ZeroIntensity
    Member

    Yeah, it would be slower and more importantly, it would break a lot of code. I think a better approach would be to design a way to generally add docstrings to arbitrary objects (such as a global __docs__ dict). But there isn't any active proposal to do that as far as I'm aware.

  10. zware commented on Jun 10, 2025

    @zware
    Member

    Another possibility would be introducing module-level property, but that would be just as slow as a regular function call. On the plus (?) side, it could also prevent redefining fundamental constants :)

  11. terryjreedy commented on Jun 11, 2025

    @terryjreedy
    Member

    Python has probably 100s of named constants. Most? are more obscure than math.e. A generic doc method would be interesting to look at. But note that sometimes the class doc is what a user wants or needs.

    Names of predefined constants should be in the index. 'e ... math module' leads to "e The mathematical constant e = 2.718281…, to available precision."

    Normal (not slotted) wuer classes can include passing a docstring on creation:

    >>> class D:
    ...     def __init__(self, doc):
    ...         self.__doc__ = doc
    ...          
    >>> help(D('d object'))
    Help on D in module __main__:
    
    <__main__.D object>
        d object
    

    Or '.doc can be added later. Builtin class instances often have no instance dict and hence no specific instance attributes.

  12. skirpichev commented on Jun 11, 2025

    @skirpichev
    Member

    Builtin class instances often have no instance dict and hence no specific instance attributes.

    Though, we can use float subclasses:

    >>> class float2(float):
    ...     pass
    ...     
    >>> mypi = float2(3.14)
    >>> mypi.__doc__ = 'spam'
    >>> help(mypi)
    Help on float2 in module __main__:
    
    3.14
        spam
    

    Edit:
    Here is a direct implementation of above:

    >>> import math
    >>> help(math.pi)
    Help on const:
    
    3.141592653589793
        This is PI!
    

    But not without a price:

    $ ./python -m timeit -s 'from math import pi; x=3.14' 'pi*x'
    2000000 loops, best of 5: 105 nsec per loop
    

    In the master:

    $ ./python -m timeit -s 'from math import pi; x=3.14' 'pi*x'
    5000000 loops, best of 5: 77.9 nsec per loop
    

    Maybe this speed difference can be mitigated with a static subclass, that will have a special getter for __doc__?

    a patch
    sk@note:~/src/cpython $ git diff
    diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
    index bbbb491156..5e7437a18d 100644
    --- a/Modules/mathmodule.c
    +++ b/Modules/mathmodule.c
    @@ -4128,11 +4128,54 @@ math_ulp_impl(PyObject *module, double x)
         return x2 - x;
     }
     
    +static PyType_Slot Const_Type_slots[] = {
    +    {Py_tp_base, NULL}, /* filled out in module exec function */
    +    {0, 0},
    +};
    +
    +static PyType_Spec Const_Type_spec = {
    +    "const",
    +    0,
    +    0,
    +    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_MANAGED_DICT,
    +    Const_Type_slots
    +};
    +
     static int
     math_exec(PyObject *module)
     {
    +    PyObject *temp;
    +
    +    /* Add Const */
    +    Const_Type_slots[0].pfunc = &PyFloat_Type;
    +    temp = PyType_FromSpec(&Const_Type_spec);
    +    if (!temp) {
    +        return -1;
    +    }
    +
    +    PyObject *pi = PyObject_CallNoArgs(temp);
     
    -    if (PyModule_Add(module, "pi", PyFloat_FromDouble(Py_MATH_PI)) < 0) {
    +    Py_DECREF(temp);
    +    if (!pi) {
    +        return -1;
    +    }
    +    ((PyFloatObject *)pi)->ob_fval = Py_MATH_PI;
    +
    +    PyObject *doc_obj = PyUnicode_FromString("This is PI!");
    +
    +    if (!doc_obj) {
    +        Py_DECREF(pi);
    +        PyErr_NoMemory();
    +        return -1;
    +    }
    +    if (PyObject_SetAttrString(pi, "__doc__", doc_obj)) {
    +        Py_DECREF(doc_obj);
    +        Py_DECREF(pi);
    +        return -1;
    +    }
    +    Py_DECREF(doc_obj);
    +    if (PyModule_Add(module, "pi", pi) < 0) {
    +        Py_DECREF(pi);
             return -1;
         }
         if (PyModule_Add(module, "e", PyFloat_FromDouble(Py_MATH_E)) < 0) {
  13. self-assigned this
    on Jun 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions