[Bug] f-string with hasattr causes an import error
Describe the bug
Using hasattr in an f-string causes an import error (There is no import error).
The same data in a .format does not produce that error.
To reproduce Steps to reproduce the behavior:
- copy this program:
import mission_run_1
missions = [("1", irrelevant_name, "first"), ("2", "stop", "stop"),]
print(f"hasattr f-string {hasattr(missions[0][1], "run")}") # import error
# print("hasattr format {}".format(hasattr(missions[0][1], "run"))) # OK name error
- make sure you have a program loaded with the module name of the import
- run the problem code and see
ImportError: no module named 'mission_run_1' - comment the first print and uncomment the second
- Run again and see no
ImportError, but the correctNameError
Expected behavior The same correct behavior for the f-string and the formatted one. And most of all: No incorrect flagging the import as error.
Took me quiet some time to assure that the import statement was correct 😄
Screenshots There is a saying that a picture is worth a 1000 words. Screenshots really help to identify and solve problems. None sofar.
Extra info
- Have looked at pybricks issues and micropython issues could not find something similar.
- I think the f-strings are re-written during compile, so the problem might be in that phase. Yet strange that the format I show does not have the problem.
- Tested on beta and stable versions.
I assume this works with pybricksdev instead of Pybricks Code?
Sounds like a bug with the Python parser we use for import detection in Pybricks Code.
I assume this works with pybricksdev instead of Pybricks Code?
Will try tonight.
[EDIT] Why not now.... @dlech
pybricksdev v1.0.0a52
pybricksdev run -n "prime beta" ble issue_1954_fstring_problem.py
Searching for prime beta...
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "C:\Python311\Scripts\pybricksdev.exe\__main__.py", line 7, in <module>
File "C:\Python311\Lib\site-packages\pybricksdev\cli\__init__.py", line 446, in main
asyncio.run(subparsers.choices[args.tool].tool.run(args))
File "C:\Python311\Lib\asyncio\runners.py", line 190, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\asyncio\runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\pybricksdev\cli\__init__.py", line 204, in run
await hub.run(script_path, args.wait)
File "C:\Python311\Lib\site-packages\pybricksdev\connections\pybricks.py", line 572, in run
mpy = await compile_multi_file(py_path, abi)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\pybricksdev\compile.py", line 121, in compile_multi_file
finder.run_script(path)
File "C:\Python311\Lib\modulefinder.py", line 153, in run_script
self.load_module('__main__', fp, pathname, stuff)
File "C:\Python311\Lib\modulefinder.py", line 332, in load_module
co = compile(fp.read(), pathname, 'exec')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "issue_1954_fstring_problem.py", line 4
print(f"hasattr f-string {hasattr(missions[0][1], "run")}") # import error
^^^
SyntaxError: f-string: unmatched '('
So in the end a user syntax error. But a nasty way of showing it. Fixed the quotes and so "fixed" the error.
Thanks for checking. So more a bug in MicroPython for not catching the syntax error.
Thanks for checking. So more a bug in MicroPython for not catching the syntax error.
Running MicroPython v1.22.1+ds-1build2 on 2024-04-01; linux [GCC 13.2.0] version
this program (removed the comments)
import mission_run_1
missions = [("1", irrelevant_name, "first"), ("2", "stop", "stop"),]
print(f"hasattr f-string {hasattr(missions[0][1], "run")}")
Shows:
Traceback (most recent call last):
File "issue_1954_fstring_problem_syntax.py", line 2, in <module>
File "/home/bert/py/pybricks/issue/mission_run_1.py", line 3, in <module>
ImportError: no module named 'pybricks'
I do not know the phases but I would expect the syntax being found before the import being done.
Will look if I can get the "source" more simple to show that the syntax error is not catched.
Bert
I see. The code works fine in MicroPython if there are no imports since MicroPython seems to allow " withing f-string with f" while standard Python does not.
pybricksdev reports a syntax error because it uses the CPython compiler to find imports.
Pybricks Code uses static analysis tool find imports and silently fails on syntax errors assuming that any syntax error it detects would also be a syntax error in MicroPython. But since this is not a syntax error in MicroPython, the program compiles and runs but the proper imports were not included because the import analyzer detected the syntax error.
minimal test case:
import a
print(f"{"x"}")
Simplified to:
print(f"{"run"}")
print("{"run"}")
The first line should report a syntax error, but in these versions the second line does.
Tested on micropython v1.22.1+ds-1build2 on 2024-04-01 and the same error I see on python3.12.3 I see no issue on Cpython. Will try to make one there.
Added cpython issue 127428 Unexpected behavior in f-strings with nested quotes
JelleZijlstra pointed to PEP 701 – Syntactic formalization of f-strings So in python 3.12 this is correct syntax.
[EDIT]
With this in mind I re-tested the f-string on MicroPython v1.22.1+ds-1build2 on 2024-04-01; linux [GCC 13.2.0] version
>>> print(f"{"run"}")
run
So leave this until pybricks uses micropython 1.22? The circumvention to use a string.format is there.
I don't see how updating the MicroPython version will make a difference. The problem is that the Python parser for finding imports in Pybricks Code hasn't been updated to support the Python 3.12 syntax. MicroPython isn't involved in this part of the code at all.
I stand corrected 😄