Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by CosmicOrbiter912

How can I enforce ASCII-only identifiers in Python while still allowing UTF-8 string literals?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I want Python to raise an error when encountering non-ASCII characters in identifiers (e.g., variable names, function names) but still allow UTF-8 encoded strings (e.g., "Привет, мир!").

For example:

PYTHON
# This should raise an error def тест(): pass # This should work text = "Привет, мир!"

I tried using # -*- coding: ascii -*-, but that blocks non-ASCII characters everywhere in the source code, including in string literals.

I also need a solution for Jupyter Notebook.

4

Comments 0

Answers 4

Views 125

4 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

There's no built‑in flag or encoding setting in Python that will only restrict non‐ASCII identifiers while still allowing non‑ASCII content inside string literals. In Python 3, Unicode identifiers are allowed by design (PEP 3131) so that you can write function names or variable names in many languages.

However, if you need to enforce that only ASCII is used in identifiers, you can use one of the following approaches:


1. Use a Linter or Static Analyzer

Tools like flake8 or pylint allow you to add custom rules. You could write a plugin or a custom checker that scans the abstract syntax tree (AST) of your source code and raises warnings or errors if any identifier contains non-ASCII characters.


2. Create a Custom Preprocessor or Import Hook

You can write a small utility that parses your module’s source code before it’s compiled. Here’s an example using Python’s ast module:

PYTHON
import ast def check_ascii_identifiers(code, filename="<unknown>"): """ Parse the source code and raise SyntaxError if any identifier contains non-ASCII characters. """ tree = ast.parse(code, filename=filename) for node in ast.walk(tree): # Check function and class names if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): if any(ord(c) > 127 for c in node.name): raise SyntaxError(f"Non-ASCII identifier in {node.__class__.__name__} '{node.name}' detected in {filename}") # Check arguments names in function definitions if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): for arg in node.args.args: if any(ord(c) > 127 for c in arg.arg): raise SyntaxError(f"Non-ASCII identifier in argument '{arg.arg}' detected in {filename}") # (Optionally, also check keyword-only arguments, vararg and kwarg) if node.args.vararg and any(ord(c) > 127 for c in node.args.vararg.arg): raise SyntaxError(f"Non-ASCII identifier in vararg '{node.args.vararg.arg}' detected in {filename}") if node.args.kwarg and any(ord(c) > 127 for c in node.args.kwarg.arg): raise SyntaxError(f"Non-ASCII identifier in kwarg '{node.args.kwarg.arg}' detected in {filename}") return tree if __name__ == "__main__": # Example source code example_code = '''\ # This should raise an error def тест(): pass # This should work text = "Привет, мир!" ''' try: check_ascii_identifiers(example_code, filename="example.py") except SyntaxError as e: print("SyntaxError:", e)

In this snippet the check_ascii_identifiers function parses the code and walks the AST checking function names, class names, and function argument names. When it finds an identifier with any character having an ordinal above 127 (i.e. non-ASCII), it raises a SyntaxError. You can expand this function to catch additional nodes (e.g., variable names in assignments) as needed.

To integrate this into your workflow, you could either:

  • Insert this check at the beginning of your script.
  • Write a custom import hook that runs these checks on module load.
  • Use it in your continuous integration (CI) pipeline to flag code that doesn't adhere.

3. For Jupyter Notebooks

Jupyter doesn’t have a built‑in mechanism to enforce identifier restrictions. However, you can define an IPython cell magic that preprocesses the cell’s source code before execution. For example:

PYTHON
from IPython.core.magic import register_cell_magic import ast def check_ascii_identifiers(code, filename="<cell>"): tree = ast.parse(code, filename=filename) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): if any(ord(c) > 127 for c in node.name): raise SyntaxError(f"Non-ASCII identifier in {node.__class__.__name__} '{node.name}' detected") if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): for arg in node.args.args: if any(ord(c) > 127 for c in arg.arg): raise SyntaxError(f"Non-ASCII identifier in argument '{arg.arg}' detected") return tree @register_cell_magic def ascii_identifiers(line, cell): # Run our check before executing the cell check_ascii_identifiers(cell) exec(cell, globals()) # Now, in a notebook cell, you can start with: # %%ascii_identifiers # def тест(): # pass # # text = "Привет, мир!"

By using this cell magic, the notebook will raise a SyntaxError if a cell contains non-ASCII identifiers while still allowing non-ASCII string literals.


Summary

  • No built‑in configuration exists: Python’s encoding directives affect the entire file, not just identifiers.
  • Custom solutions are required: Either by using a linter/static analyzer or by writing a custom preprocessor/import hook.
  • Jupyter Notebooks: Similar techniques—such as a cell magic—can be applied to enforce the restriction.

These approaches let you enforce ASCII-only identifiers while keeping the flexibility of UTF-8 encoded strings.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by MartianWayfarer543 1 month ago

0

This is easily checked with static code analysis.
Pylint will report an issue in its default configuration:

PLAINTEXT
foo.py:2:0: C2401: Function name "тест" contains a non-ASCII character, consider renaming it. (non-ascii-name)

You should configure your VCS to run pylint and only accept commits without warnings; or at least without C2401.

No comments yet.

Answer by CosmicSatellite758 1 month ago

0

I second the answer of @Friedrich as it is good practice for "real" projects, but for completeness you can try to work from the output of the dir() function :

PYTHON
def check_ascii(args: list[str]) -> bool: for s in args: if not s.isascii(): return False return True a, b, c = 5, 7, 0 print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'check_ascii', 'a', 'b', 'c'] check_ascii(dir()) # True èé = True print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'check_ascii', 'a', 'b', 'c', 'èé'] check_ascii(dir()) # False

Edit: you could be tempted to put the dir() inside check_ascii's body but it won't work as the scope would not be the same:

PYTHON
def check_dir(): print(dir()) check_dir() # []

No comments yet.

Answer by MeteoricCommander503 1 month ago

0

While @Friedrich's answer using pylint works for most cases, it has to be noted that pylint is a third-party library that is prone to falling out of sync with each major release of Python. For example, pylint to this date still does not recognize the match statement, which became part of Python's syntax with the release of Python 3.10 back in 2021. You can try running pylint against the code below to find it not warning about a non-ASCII name:

PYTHON
match тест: case _: pass

And @globglogabgalab's answer using dir() works only for names defined in the module's global namespace and only those that happen to be defined in the current execution path.

An arguably more robust approach would be to take advantage of the convention that all names are parsed into AST as either the id attribute of a ast.Name node or the name attribute of other name-including node types, derived from the base class ast.AST:

PYTHON
import ast with open(__file__, encoding='utf-8') as source: for node in ast.walk(ast.parse(source.read())): match node: case ast.Name(id=name): pass case ast.AST(name=name) if name: pass case _: continue if not name.isascii(): raise RuntimeError(f'{name} not an ASCII identifier.') def тест(): pass text = "Привет, мир!"

This produces:

PYTHON
RuntimeError: тест not an ASCII identifier.

Demo here

This approach is more future-proof because it is highly unlikely that Python developers stop following this convention in any future syntax changes.

No comments yet.

Discussion

No comments yet.