Skip to content

Improve error message when trying to delete builtin #138890

Closed as not planned
Closed as not planned

Description

@sergey-miryanov

Feature or enhancement

Proposal:

Following code produces not very clear error message (IMHO)

>>> all
<built-in function all>
>>> all in globals()
False
>>> del all
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    del all
        ^^^
NameError: name 'all' is not defined

On my sight it well be better to tell that builtin cannot be deleted.

(fixed can be to cannot be)

Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

No response

Linked PRs

Activity

  1. Tapeline commented on Sep 14, 2025

    @Tapeline
    Contributor

    We could add a clearer error message, e.g.:

    >>> del all
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        del all
            ^^^
    NameError: cannot delete builtin 'all'
  2. StanFromIreland commented on Sep 14, 2025

    @StanFromIreland
    Member

    Prior art: in b86ed8e a range of cases was covered, e.g.:

    >>> del 11
      File "<python-input-2>", line 1
        del 11
            ^^
    SyntaxError: cannot delete literal
    
  3. sergey-miryanov commented on Sep 14, 2025

    @sergey-miryanov
    ContributorAuthor

    Hm, I'm not sure it should be handled while parsing. Or I don't get what you mean.

  4. StanFromIreland commented on Sep 14, 2025

    @StanFromIreland
    Member

    It is currently handled in the parser,

    cpython/Parser/pegen.h

    Lines 238 to 258 in 8b5ce31

    Py_LOCAL_INLINE(void *)
    _RAISE_SYNTAX_ERROR_INVALID_TARGET(Parser *p, TARGETS_TYPE type, void *e)
    {
    expr_ty invalid_target = CHECK_NULL_ALLOWED(expr_ty, _PyPegen_get_invalid_target(e, type));
    if (invalid_target != NULL) {
    const char *msg;
    if (type == STAR_TARGETS || type == FOR_TARGETS) {
    msg = "cannot assign to %s";
    }
    else {
    msg = "cannot delete %s";
    }
    return RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
    invalid_target,
    msg,
    _PyPegen_get_expr_name(invalid_target)
    );
    return RAISE_SYNTAX_ERROR_KNOWN_LOCATION(invalid_target, "invalid syntax");
    }
    return NULL;
    }

  5. sergey-miryanov commented on Sep 14, 2025

    @sergey-miryanov
    ContributorAuthor

    Why are you talking about literals? Builtin all is not a literal. And following is a valid Python code:

    all = 'xxx'
    del all
    

    So all is not an invalid target. Or I still not get you, sorry.

  6. StanFromIreland commented on Sep 14, 2025

    @StanFromIreland
    Member

    I am talking about the existing improved cases.

  7. sergey-miryanov commented on Sep 14, 2025

    @sergey-miryanov
    ContributorAuthor

    Ough, ok. Thanks!

  8. StanFromIreland commented on Sep 14, 2025

    @StanFromIreland
    Member

    Anyhow, a quick patch:

    Subject: [PATCH] Commit
    ---
    Index: Python/codegen.c
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
    diff --git a/Python/codegen.c b/Python/codegen.c
    --- a/Python/codegen.c	(revision 8b5ce31c2b44d9bf82e6119e90a52dd530bfd1db)
    +++ b/Python/codegen.c	(date 1757858640025)
    @@ -3278,7 +3278,14 @@
                     : LOAD_NAME;
                 break;
             case Store: op = STORE_NAME; break;
    -        case Del: op = DELETE_NAME; break;
    +        case Del:
    +            PyObject *builtins = PyEval_GetBuiltins();
    +            if (PyDict_Contains(builtins, mangled)) {
    +                _PyCompile_Error(c, loc, "cannot delete builtin");
    +                goto error;
    +            }
    +            op = DELETE_NAME;
    +            break;
             }
             break;
         }

    Though I worry about performance cost.

  9. sobolevn commented on Sep 14, 2025

    @sobolevn
    Member

    I think that we can't do that in compiler, because users can shadow buitlins:

    >>> all = True
    >>> del all

    This must continue to work. DELETE_NAME is the most probable place for this.

  10. Tapeline commented on Sep 15, 2025

    @Tapeline
    Contributor

    Maybe the best place to put this check is in err ≠ 0 block of DELETE_NAME instruction:

    PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
    PyObject *ns = LOCALS();
    int err;
    if (ns == NULL) {
        _PyErr_Format(tstate, PyExc_SystemError,
                      "no locals when deleting %R", name);
        ERROR_NO_POP();
    }
    err = PyObject_DelItem(ns, name);
    // Can't use ERROR_IF here.
    if (err != 0) {
        // if name in builtins -> cannot delete builtin
        // else ->
        _PyEval_FormatExcCheckArg(tstate, PyExc_NameError,
                                  NAME_ERROR_MSG,
                                  name);
        ERROR_NO_POP();
    }
  11. sergey-miryanov commented on Sep 15, 2025

    @sergey-miryanov
    ContributorAuthor

    You also have to do same check in DELETE_GLOBAL and DELETE_FAST.

  12. Tapeline commented on Sep 17, 2025

    @Tapeline
    Contributor

    I'd like to solve this issue :)

  13. sergey-miryanov commented on Sep 17, 2025

    @sergey-miryanov
    ContributorAuthor

    Yeah, go ahead. Feel free to ask if you have some questions!

  14. changed the title [-][enchancement] Fix error message when trying to delete builtin[/-] [+]Improve error message when trying to delete builtin[/+] on Sep 17, 2025
  15. TeamSpen210 commented on Oct 14, 2025

    @TeamSpen210

    Not sure about the wording here, since it's not entirely true - you can delete builtins from the module, just not via global scopes. On the other hand it's probably not the best to encourage people to delete them.

  16. terryjreedy commented on Oct 27, 2025

    @terryjreedy
    Member

    I don't see a real issue here and think that this could be closed.

    As documented, del only deletes name in the current local namespace (unless the name is declared global).

    Deletion of a name removes the binding of that name from the local or global namespace, depending on whether the name occurs in a global statement in the same code block. Trying to delete an unbound name raises a NameError exception.

    At global scope, the global namespace is the local namespace. The builtins namespace is separate from globals. Beginners should be taught this, along with the automagic lookup behavior.

    To delete from builtins, one must import the module. import builtins; delete builtins.all. (I not sure this should be advertised.)

    Perhaps 'unbound name' could be expanded to 'name not bound in the appropriate namespace', but it is not completely unreasonable to expect people to understand this.

  17. terryjreedy commented on Oct 27, 2025

    @terryjreedy
    Member

    Discourse thread proposed a change to all name error messages. The more restrained proposal here looks better to me, but I propose even less change on the PR.

  18. terryjreedy commented on Oct 27, 2025

    @terryjreedy
    Member

    Still, the premise of this issue is that the user really meant to delete the builtin all rather than something else. Consider

    ale = 3
    del all
    Traceback (most recent call last):
      File "<pyshell#17>", line 1, in <module>
        del all
    NameError: name 'all' is not defined. Did you mean: 'ale'?
    

    The current message is correct and gives a helpful hint. The PR will degrade it to something irrelevant.

  19. Tapeline commented on Oct 29, 2025

    @Tapeline
    Contributor

    The current message is correct and gives a helpful hint. The PR will degrade it to something irrelevant.

    I have checked, surprisingly, it does not:

    Python 3.15.0a0 (heads/fix-gh-138890-dirty:8bf802d87ca, Sep 19 2025, 17:20:43) [MSC v.1943 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> ale = 1
    >>> del all
    Traceback (most recent call last):
      File "<python-input-1>", line 1, in <module>
        del all
            ^^^
    NameError: cannot delete builtin 'all'. Did you mean: 'ale'?
  20. Locked-chess-official commented on Oct 30, 2025

    @Locked-chess-official
    Contributor

    The current message is correct and gives a helpful hint. The PR will degrade it to something irrelevant.

    However, the next isn't:

    Python 3.14.0 (tags/v3.14.0:ebf955d, Oct  7 2025, 10:15:03) [MSC v.1944 64 bit (AMD64)] on win32
    >>> del next
    Traceback (most recent call last):
      File "<python-input-0>", line 1, in <module>
        del next
            ^^^^
    NameError: name 'next' is not defined. Did you mean: 'anext'?
    

    Maybe it need an attribute to distinguish whether the NameError raised from getting or deleting to decide whether add f_builtins to the list of possible suggestion.

  21. merwok commented on Jul 8, 2026

    @merwok
    Member

    I agree with Terry, the current message is technically correct and clear enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions