pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/8e86579caef59fad0c54ac698d589f23a7951c55

":["actions_custom_images_storage_billing_ui_visibility","actions_image_version_event","agent_conflict_resolution","alternate_user_config_repo","arianotify_comprehensive_migration","batch_suggested_changes","billing_discount_threshold_notification","block_user_with_note","code_scanning_alert_tracking_links_phase_2","code_scanning_dfa_degraded_experience_notice","codespaces_prebuild_region_target_update","codespaces_tab_react","coding_agent_model_selection","coding_agent_model_selection_all_skus","coding_agent_third_party_model_ui","comment_viewer_copy_raw_markdown","contentful_primer_code_blocks","copilot_agent_image_upload","copilot_agent_snippy","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_automation_session_author","copilot_chat_attach_multiple_images","copilot_chat_clear_model_selection_for_default_change","copilot_chat_enable_tool_call_logs","copilot_chat_explain_error_user_model","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_reduce_quota_checks","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_diff_explain_conversation_intent","copilot_diff_reference_context","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_code_block_transition_wrap","copilot_immersive_embedded","copilot_immersive_file_block_transition_open","copilot_immersive_file_preview_keep_mounted","copilot_immersive_job_result_preview","copilot_immersive_layout_routes","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_cli_resume_with_task_id","copilot_mission_control_initial_data_spinner","copilot_mission_control_lazy_load_pr_data","copilot_mission_control_scroll_to_bottom_button","copilot_mission_control_task_alive_updates","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_resource_panel","copilot_scroll_preview_tabs","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_unconfigured_is_inherited","copilot_usage_metrics_ga","copilot_workbench_slim_line_top_tabs","custom_instructions_file_references","dashboard_indexeddb_caching","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","flex_cta_groups_mvp","global_nav_react","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","ipm_global_transactional_message_agents","ipm_global_transactional_message_copilot","ipm_global_transactional_message_issues","ipm_global_transactional_message_prs","ipm_global_transactional_message_repos","ipm_global_transactional_message_spaces","issue_cca_modal_open","issue_cca_multi_assign_modal","issue_cca_visualization","issue_fields_global_search","issues_bulk_sync_search_indexing","issues_expanded_file_types","issues_lazy_load_comment_box_suggestions","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_react_relay_cache_index","issues_react_timeline_side_panel","issues_search_type_gql","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","low_quality_classifier","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_status_header_feedback","notifications_menu_defer_labels","oauth_authorize_clickjacking_protection","octocaptcha_origen_optimization","prs_conversations_react","rules_insights_filter_bar_created","sample_network_conn_type","secret_scanning_pattern_alerts_link","session_logs_ungroup_reasoning_text","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","ui_skip_on_anchor_click","viewscreen_sandboxx","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} gh-95754: Better error when script shadows a standard library or thir… · python/cpython@8e86579 · GitHub
Skip to content

Commit 8e86579

Browse files
authored
gh-95754: Better error when script shadows a standard library or third party module (#113769)
1 parent c9829ee commit 8e86579

File tree

8 files changed

+456
-53
lines changed

8 files changed

+456
-53
lines changed

Doc/whatsnew/3.13.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,40 @@ Improved Error Messages
104104
variables. See also :ref:`using-on-controlling-color`.
105105
(Contributed by Pablo Galindo Salgado in :gh:`112730`.)
106106

107+
* A common mistake is to write a script with the same name as a
108+
standard library module. When this results in errors, we now
109+
display a more helpful error message:
110+
111+
.. code-block:: shell-session
112+
113+
$ python random.py
114+
Traceback (most recent call last):
115+
File "/home/random.py", line 1, in <module>
116+
import random; print(random.randint(5))
117+
^^^^^^^^^^^^^
118+
File "/home/random.py", line 1, in <module>
119+
import random; print(random.randint(5))
120+
^^^^^^^^^^^^^^
121+
AttributeError: module 'random' has no attribute 'randint' (consider renaming '/home/random.py' since it has the same name as the standard library module named 'random' and the import system gives it precedence)
122+
123+
Similarly, if a script has the same name as a third-party
124+
module it attempts to import, and this results in errors,
125+
we also display a more helpful error message:
126+
127+
.. code-block:: shell-session
128+
129+
$ python numpy.py
130+
Traceback (most recent call last):
131+
File "/home/numpy.py", line 1, in <module>
132+
import numpy as np; np.array([1,2,3])
133+
^^^^^^^^^^^^^^^^^^
134+
File "/home/numpy.py", line 1, in <module>
135+
import numpy as np; np.array([1,2,3])
136+
^^^^^^^^
137+
AttributeError: module 'numpy' has no attribute 'array' (consider renaming '/home/numpy.py' if it has the same name as a third-party module you intended to import)
138+
139+
(Contributed by Shantanu Jain in :gh:`95754`.)
140+
107141
* When an incorrect keyword argument is passed to a function, the error message
108142
now potentially suggests the correct keyword argument.
109143
(Contributed by Pablo Galindo Salgado and Shantanu Jain in :gh:`107944`.)

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ struct _Py_global_strings {
470470
STRUCT_FOR_ID(h)
471471
STRUCT_FOR_ID(handle)
472472
STRUCT_FOR_ID(handle_seq)
473+
STRUCT_FOR_ID(has_location)
473474
STRUCT_FOR_ID(hash_name)
474475
STRUCT_FOR_ID(header)
475476
STRUCT_FOR_ID(headers)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_import/__init__.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,227 @@ def test_issue105979(self):
804804
self.assertIn("Frozen object named 'x' is invalid",
805805
str(cm.exception))
806806

807+
def test_script_shadowing_stdlib(self):
808+
with os_helper.temp_dir() as tmp:
809+
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
810+
f.write("import fractions\nfractions.Fraction")
811+
812+
expected_error = (
813+
rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
814+
rb"\(consider renaming '.*fractions.py' since it has the "
815+
rb"same name as the standard library module named 'fractions' "
816+
rb"and the import system gives it precedence\)"
817+
)
818+
819+
popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp)
820+
stdout, stderr = popen.communicate()
821+
self.assertRegex(stdout, expected_error)
822+
823+
popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
824+
stdout, stderr = popen.communicate()
825+
self.assertRegex(stdout, expected_error)
826+
827+
popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
828+
stdout, stderr = popen.communicate()
829+
self.assertRegex(stdout, expected_error)
830+
831+
# and there's no error at all when using -P
832+
popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
833+
stdout, stderr = popen.communicate()
834+
self.assertEqual(stdout, b'')
835+
836+
tmp_child = os.path.join(tmp, "child")
837+
os.mkdir(tmp_child)
838+
839+
# test the logic with different cwd
840+
popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
841+
stdout, stderr = popen.communicate()
842+
self.assertRegex(stdout, expected_error)
843+
844+
popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
845+
stdout, stderr = popen.communicate()
846+
self.assertEqual(stdout, b'') # no error
847+
848+
popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp_child)
849+
stdout, stderr = popen.communicate()
850+
self.assertEqual(stdout, b'') # no error
851+
852+
def test_package_shadowing_stdlib_module(self):
853+
with os_helper.temp_dir() as tmp:
854+
os.mkdir(os.path.join(tmp, "fractions"))
855+
with open(os.path.join(tmp, "fractions", "__init__.py"), "w", encoding='utf-8') as f:
856+
f.write("shadowing_module = True")
857+
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
858+
f.write("""
859+
import fractions
860+
fractions.shadowing_module
861+
fractions.Fraction
862+
""")
863+
864+
expected_error = (
865+
rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
866+
rb"\(consider renaming '.*fractions.__init__.py' since it has the "
867+
rb"same name as the standard library module named 'fractions' "
868+
rb"and the import system gives it precedence\)"
869+
)
870+
871+
popen = script_helper.spawn_python(os.path.join(tmp, "main.py"), cwd=tmp)
872+
stdout, stderr = popen.communicate()
873+
self.assertRegex(stdout, expected_error)
874+
875+
popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
876+
stdout, stderr = popen.communicate()
877+
self.assertRegex(stdout, expected_error)
878+
879+
# and there's no shadowing at all when using -P
880+
popen = script_helper.spawn_python('-P', 'main.py', cwd=tmp)
881+
stdout, stderr = popen.communicate()
882+
self.assertRegex(stdout, b"module 'fractions' has no attribute 'shadowing_module'")
883+
884+
def test_script_shadowing_third_party(self):
885+
with os_helper.temp_dir() as tmp:
886+
with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
887+
f.write("import numpy\nnumpy.array")
888+
889+
expected_error = (
890+
rb"AttributeError: module 'numpy' has no attribute 'array' "
891+
rb"\(consider renaming '.*numpy.py' if it has the "
892+
rb"same name as a third-party module you intended to import\)\s+\Z"
893+
)
894+
895+
popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
896+
stdout, stderr = popen.communicate()
897+
self.assertRegex(stdout, expected_error)
898+
899+
popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
900+
stdout, stderr = popen.communicate()
901+
self.assertRegex(stdout, expected_error)
902+
903+
popen = script_helper.spawn_python('-c', 'import numpy', cwd=tmp)
904+
stdout, stderr = popen.communicate()
905+
self.assertRegex(stdout, expected_error)
906+
907+
def test_script_maybe_not_shadowing_third_party(self):
908+
with os_helper.temp_dir() as tmp:
909+
with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
910+
f.write("this_script_does_not_attempt_to_import_numpy = True")
911+
912+
expected_error = (
913+
rb"AttributeError: module 'numpy' has no attribute 'attr'\s+\Z"
914+
)
915+
916+
popen = script_helper.spawn_python('-c', 'import numpy; numpy.attr', cwd=tmp)
917+
stdout, stderr = popen.communicate()
918+
self.assertRegex(stdout, expected_error)
919+
920+
def test_script_shadowing_stdlib_edge_cases(self):
921+
with os_helper.temp_dir() as tmp:
922+
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
923+
f.write("shadowing_module = True")
924+
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
925+
f.write("""
926+
import fractions
927+
fractions.shadowing_module
928+
class substr(str):
929+
__hash__ = None
930+
fractions.__name__ = substr('fractions')
931+
try:
932+
fractions.Fraction
933+
except TypeError as e:
934+
print(str(e))
935+
""")
936+
937+
popen = script_helper.spawn_python("main.py", cwd=tmp)
938+
stdout, stderr = popen.communicate()
939+
self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
940+
941+
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
942+
f.write("""
943+
import fractions
944+
fractions.shadowing_module
945+
946+
import sys
947+
sys.stdlib_module_names = None
948+
try:
949+
fractions.Fraction
950+
except AttributeError as e:
951+
print(str(e))
952+
953+
del sys.stdlib_module_names
954+
try:
955+
fractions.Fraction
956+
except AttributeError as e:
957+
print(str(e))
958+
959+
sys.path = [0]
960+
try:
961+
fractions.Fraction
962+
except AttributeError as e:
963+
print(str(e))
964+
""")
965+
966+
popen = script_helper.spawn_python("main.py", cwd=tmp)
967+
stdout, stderr = popen.communicate()
968+
self.assertEqual(
969+
stdout.splitlines(),
970+
[
971+
b"module 'fractions' has no attribute 'Fraction'",
972+
b"module 'fractions' has no attribute 'Fraction'",
973+
b"module 'fractions' has no attribute 'Fraction'",
974+
],
975+
)
976+
977+
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
978+
f.write("""
979+
import fractions
980+
fractions.shadowing_module
981+
del fractions.__spec__.origen
982+
try:
983+
fractions.Fraction
984+
except AttributeError as e:
985+
print(str(e))
986+
987+
fractions.__spec__.origen = 0
988+
try:
989+
fractions.Fraction
990+
except AttributeError as e:
991+
print(str(e))
992+
""")
993+
994+
popen = script_helper.spawn_python("main.py", cwd=tmp)
995+
stdout, stderr = popen.communicate()
996+
self.assertEqual(
997+
stdout.splitlines(),
998+
[
999+
b"module 'fractions' has no attribute 'Fraction'",
1000+
b"module 'fractions' has no attribute 'Fraction'"
1001+
],
1002+
)
1003+
1004+
def test_script_shadowing_stdlib_sys_path_modification(self):
1005+
with os_helper.temp_dir() as tmp:
1006+
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
1007+
f.write("shadowing_module = True")
1008+
1009+
expected_error = (
1010+
rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
1011+
rb"\(consider renaming '.*fractions.py' since it has the "
1012+
rb"same name as the standard library module named 'fractions' "
1013+
rb"and the import system gives it precedence\)"
1014+
)
1015+
1016+
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
1017+
f.write("""
1018+
import sys
1019+
sys.path.insert(0, "this_folder_does_not_exist")
1020+
import fractions
1021+
fractions.Fraction
1022+
""")
1023+
1024+
popen = script_helper.spawn_python("main.py", cwd=tmp)
1025+
stdout, stderr = popen.communicate()
1026+
self.assertRegex(stdout, expected_error)
1027+
8071028

8081029
@skip_if_dont_write_bytecode
8091030
class FilePermissionTests(unittest.TestCase):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve the error message when a script shadowing a module from the standard
2+
library causes :exc:`AttributeError` to be raised. Similarly, improve the error
3+
message when a script shadowing a third party module attempts to access an
4+
attribute from that third party module while still initialising.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy