{"slug": "python-3-15-for-data-scientists-lazy-imports-frozendict-and-tachyon", "title": "Python 3.15 for Data Scientists: Lazy Imports, frozendict, and Tachyon", "summary": "Python 3.15, released October 9, 2026, adds a lazy import keyword (PEP 810) that delays module loading until first use, cutting a test script's median startup time from 280 ms to 14 ms across 20 runs, roughly 20 times faster. The release also introduces a built-in frozendict (PEP 814) that raises a TypeError on item assignment and requires creating a new frozendict with | to change a value, preventing pipeline functions from silently mutating shared configuration. The changes matter to data scientists because they reduce startup cost from heavy libraries such as pandas and make configuration objects safe to pass between pipeline steps.", "body_md": "Table of Contents\n\nIntroduction\n\n[Python 3.15](https://docs.python.org/3.15/whatsnew/3.15.html) was released on October 9, 2026. The changelog is long, so this article picks the five changes you’re most likely to care about as a data scientist.\n\nFor each feature, you’ll see the problem with the old way and how the new feature solves it.\n\n  Stay Current with CodeCut\n\n  Easy-to-digest articles on Python, AI, and open-source tools. Delivered twice a week.\n\n    .codecut-subscribe-wrap {\n\n        display: flex;\n\n        justify-content: center;\n\n    }\n\n    .codecut-subscribe-btn {\n\n        background: #72BEFA !important;\n\n        color: #2F2D2E !important;\n\n        border: none;\n\n        border-radius: 8px;\n\n        padding: 12px 28px;\n\n        font-family: inherit;\n\n        font-size: 16px;\n\n        font-weight: 700;\n\n        cursor: pointer;\n\n        text-decoration: none !important;\n\n        display: inline-flex;\n\n        align-items: center;\n\n        justify-content: center;\n\n        transition: background 0.3s ease;\n\n    }\n\n    .codecut-subscribe-btn:hover,\n\n    .codecut-subscribe-btn:focus {\n\n        background: #5aa8e8 !important;\n\n        color: #2F2D2E !important;\n\n        text-decoration: none !important;\n\n    }\n\n    /* Mobile responsive */\n\n    @media (max-width: 480px) {\n\n        .codecut-subscribe-btn {\n\n            width: 100%;\n\n            text-align: center;\n\n        }\n\n    }\n\nLazy Imports: Load Libraries Only When You Use Them\n\nA normal import loads a module right when Python reaches the import line:\n\nimport pandas as pd  # pandas loads here, even if the script never uses it\n\nprint(\"hello\")\n\nThe usual workaround is to move import pandas inside the function that needs it. That works, but the imports end up spread across functions. Anyone reading the code has to check every function to see which libraries it uses.\n\ndef load_trips(path):\n\n    import pandas as pd\n\n    return pd.read_parquet(path)\n\nIn Python 3.15, the lazy keyword ([PEP 810](https://peps.python.org/pep-0810/)) lets the import stay at the top while delaying the load until the module is first used:\n\nlazy import pandas as pd  # nothing loads yet\n\nprint(\"hello\")\n\ndf = pd.read_parquet(\"taxi.parquet\")  # pandas loads here, on first use\n\nTo measure the difference, compare two scripts that differ only in the import line. Neither uses pandas, so the lazy script should skip loading it entirely and run much faster:\n\n# normal.py\n\nimport pandas as pd\n\nprint(\"hello\")\n\n# lazy.py\n\nlazy import pandas as pd\n\nprint(\"hello\")\n\nRunning each script 20 times and taking the median gives:\n\nOutputnormal: 280 ms\n\nlazy:    14 ms <--- 20 times faster\n\nThe lazy version starts about 20 times faster. This shows that it never loads pandas.\n\nfrozendict: Immutable Dicts, Built In\n\n[Plain dicts](https://codecut.ai/hidden-cost-python-dictionaries-safer-alternatives/) can be changed by any function they’re passed to. In a pipeline, that means one step can quietly change a setting that later steps rely on.\n\nconfig = {\"min_fare\": 0, \"max_fare\": 500}\n\ndef drop_outliers(cfg):\n\n    cfg[\"max_fare\"] = 1000  # meant as a local tweak\n\nprint(\"before:\", config)\n\ndrop_outliers(config)\n\nprint(\"after: \", config)\n\nOutputbefore: {'min_fare': 0, 'max_fare': 500}\n\nafter:  {'min_fare': 0, 'max_fare': 1000}   <- max_fare changed\n\nPython 3.15 solves this problem with a built-in frozendict ([PEP 814](https://peps.python.org/pep-0814/)), a dict that can’t be changed after it’s created:\n\nconfig = frozendict(min_fare=0, max_fare=500)\n\ndef drop_outliers(cfg):\n\n    cfg[\"max_fare\"] = 1000\n\ndrop_outliers(config)\n\nOutputTypeError: 'frozendict' object does not support item assignment\n\nTo change a value, create a new frozendict with |. The original stays the same:\n\nstrict_config = config | {\"max_fare\": 300}\n\nprint(\"original:\", config)\n\nprint(\"new:     \", strict_config)\n\nOutputoriginal: frozendict({'min_fare': 0, 'max_fare': 500})\n\nnew:      frozendict({'min_fare': 0, 'max_fare': 300})\n\nChanging a setting now takes a deliberate step, so it can’t happen by accident.\n\n📚 For comprehensive guidance on managing pipeline configs, check out [Production-Ready Data Science](https://codecut.ai/production-ready-data-science/).\n\nUnpacking in Comprehensions: Flatten and Merge in One Line\n\nYou can flatten a list of lists with a double for or [itertools.chain.from_iterable](https://codecut.ai/itertools-feature-engineering-essentials/). However, the double for is hard to read at a glance.\n\ncolumn_groups = [\n\n    [\"fare_amount\", \"tip_amount\"],\n\n    [\"trip_distance\"],\n\n    [\"passenger_count\", \"payment_type\"],\n\n]\n\nprint([column for group in column_groups for column in group])\n\nOutput['fare_amount', 'tip_amount', 'trip_distance', 'passenger_count', 'payment_type']\n\nPython 3.15 makes this more readable by allowing * inside comprehensions ([PEP 798](https://peps.python.org/pep-0798/)). It unpacks each inner list, so the items end up in one flat list:\n\nprint([*group for group in column_groups])\n\nThe same syntax works for sets:\n\nmonthly_payment_types = [{1, 2}, {2, 3, 4}, {1, 5}]\n\nprint({*types for types in monthly_payment_types})\n\nOutput{1, 2, 3, 4, 5}\n\n** allows merging a list of dicts into one. If two dicts share a key, the dict that comes later in the list overrides the earlier one.\n\nlayers = [\n\n    {\"min_fare\": 0, \"max_fare\": 500},  # defaults\n\n    {\"max_fare\": 300},                 # overrides max_fare\n\n    {\"payment_types\": (1, 2)},         # adds a new key\n\n]\n\nprint({**layer for layer in layers})\n\nOutput{'min_fare': 0, 'max_fare': 300, 'payment_types': (1, 2)}\n\nTachyon: Profile a Running Job Without Restarting It\n\nTachyon vs cProfile\n\nA profiler tells you which parts of your code take the most time. Python’s built-in profiler, cProfile, times every function call, even when the same function runs millions of times. That extra work makes the program run slower:\n\ncall 1   func_1   start timer ... stop timer\n\ncall 2   func_1   start timer ... stop timer   <- same function, timed again\n\ncall 3   func_2   start timer ... stop timer\n\n...               every call gets timed -> extra work on each one\n\nTachyon, the new profiler in Python 3.15, doesn’t time every function call. Instead, it checks which function is running once every millisecond. A function that shows up in 3 of 4 checks used about 75% of the time:\n\n1 ms   func_1\n\n2 ms   func_1\n\n3 ms   func_2\n\n4 ms   func_1\n\nfunc_1   3 of 4 checks = 75% of the time\n\nfunc_2   1 of 4 checks = 25% of the time\n\nProfile with Tachyon\n\nLet’s try profiling a pipeline with Tachyon. Start by installing pyarrow and downloading one month of [NYC Yellow Taxi trip records](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page):\n\nuv pip install pyarrow\n\ncurl -o taxi.parquet https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2024-01.parquet\n\nCreate a simple data pipeline:\n\n# pipeline.py\n\nimport pandas as pd\n\ndef load_trips(path):\n\n    columns = [\"PULocationID\", \"fare_amount\", \"tip_amount\", \"trip_distance\"]\n\n    return pd.read_parquet(path, columns=columns)\n\ndef clean_trips(trips):\n\n    return trips[(trips[\"fare_amount\"] > 0) & (trips[\"trip_distance\"] > 0)]\n\ndef tip_rate(row):\n\n    return row[\"tip_amount\"] / row[\"fare_amount\"]\n\ndef add_tip_rate(trips):\n\n    return trips.assign(tip_rate=trips.apply(tip_rate, axis=1))\n\ndef summarize_zones(trips):\n\n    return trips.groupby(\"PULocationID\")[\"tip_rate\"].median()\n\ntrips = load_trips(\"taxi.parquet\")\n\ntrips = clean_trips(trips)\n\ntrips = add_tip_rate(trips)\n\nsummary = summarize_zones(trips)\n\nprint(summary.sort_values(ascending=False).head(3))\n\nTo run profiling with Tachyon, run:\n\npython -m profiling.sampling run --sort cumtime -l 9 pipeline.py\n\nAdd the --flamegraph option to write an interactive HTML report:\n\npython -m profiling.sampling run --flamegraph -o flamegraph.html pipeline.py\n\nEach bar is a function. Bars below it are the functions it called. A bar’s width and color show how much of the run time it took:\n\norange-red    60% or more\n\nyellow        35% to 60%\n\ngreen         18% to 35%\n\nblue          under 18%\n\nHover over a bar to see its details. add_tip_rate takes 78.5% of the run, so it’s the slow step:\n\nadd_tip_rate calls tip_rate through pandas apply, once for every row. tip_rate alone takes 54.2% of the run:\n\nThe narrow stack on the right is import pandas as pd, at 18.6% of the run. The import is slow because pandas loads 293 submodules, and each one runs its own setup code:\n\nSo the slow part of this pipeline is the row-by-row apply in add_tip_rate, not loading the data.\n\nTo understand how Tachyon got these results, look at the Profile Summary panel in the sidebar. It shows that Tachyon:\n\nCollected 3.1K samples in 5.52 seconds\n\nSaw 261 different functions in those samples\n\nSucceeded on 56.7% of its checks (sampling efficiency)\n\nTachyon also barely slows the program down. It added about 4% to the run time, while cProfile made it more than 3 times slower:\n\npython pipeline.py                              ~4.9 s    no profiler\n\npython -m profiling.sampling run pipeline.py    ~5.1 s    Tachyon: +4%\n\npython -m cProfile pipeline.py                  ~16.7 s   cProfile: 3.4x slower\n\nAttach to a Running Process\n\ncProfile can’t attach to a program that’s already running. To profile a long job with it, you’d have to restart the job.\n\nTachyon can attach to a job that’s already running, without restarting it.\n\nThe command below samples a running job for 5 seconds. Replace <PID> with your job’s process ID:\n\npython -m profiling.sampling attach -d 5 <PID>\n\nTo find the process ID, search the running processes for your script’s name with pgrep:\n\npgrep -af pipeline.py   # Linux; on macOS, use pgrep -lf pipeline.py\n\nOutput27086 python pipeline.py -> 27086 is the <PID>\n\nClearer Error Messages\n\nHints for Missing Attributes\n\nMissing-attribute errors now include a suggestion for what you likely meant, not just a note that the attribute is missing.\n\nLet’s say you keep each experiment’s data in a dataclass, then call .shape on the dataclass instead of on its DataFrame. In Python 3.14 and earlier, the error only says the attribute is missing:\n\nfrom dataclasses import dataclass\n\nimport pandas as pd\n\n@dataclass\n\nclass Experiment:\n\n    name: str\n\n    data: pd.DataFrame\n\nexp = Experiment(name=\"baseline\", data=pd.DataFrame({\"fare\": [12.5, 8.0]}))\n\nexp.shape\n\nOutputAttributeError: 'Experiment' object has no attribute 'shape'\n\nPython 3.15 points to where the attribute actually is:\n\nOutputAttributeError: 'Experiment' object has no attribute 'shape'. Did you mean '.data.shape' instead of '.shape'?\n\nHints for Method Names from Other Languages\n\nThose come from other languages such as JavaScript might get confused of syntax. For example, calling a JavaScript-style push on a list, or append on a tuple:\n\n[\"fare\", \"tip\"].push(\"distance\")\n\n(\"fare\", \"tip\").append(\"distance\")\n\nPython 3.14 simply says the method is missing:\n\nOutputAttributeError: 'list' object has no attribute 'push'\n\nAttributeError: 'tuple' object has no attribute 'append'\n\nPython 3.15 figures out the correct syntax and gives a hint:\n\nOutputAttributeError: 'list' object has no attribute 'push'. Did you mean '.append'?\n\nAttributeError: 'tuple' object has no attribute 'append'. Did you mean to use a 'list' object?\n\nSmaller Improvements Worth Knowing\n\nPython 3.15 also includes smaller changes that make everyday work smoother.\n\nUTF-8 by Default: Fewer Cross-Platform CSV Bugs\n\nA file stores bytes, not letters. An encoding is the rule for turning those bytes back into letters. A few common ones:\n\nencoding   characters it covers                   where you see it\n\n--------   ------------------------------------   ----------------------------------\n\nascii      128: English letters, digits, symbols  old systems; no é, no emoji\n\ncp1252     256: adds Western European letters     default on many Windows machines\n\nutf-8      every character, including 日本 and 🙂   macOS, Linux, and most of the web\n\nBefore 3.15, if you called open() without saying which rule to use, Python picked based on the computer. So the same script gives different results on different machines. Code that works on your laptop can quietly misread files on a teammate’s.\n\nsame file, saved as UTF-8:   c  a  f  é\n\n                             bytes: 63 61 66 C3 A9    (é takes 2 bytes)\n\nMac reads it with UTF-8       -> café       correct\n\nWindows reads it with cp1252  -> cafÃ©      wrong letters\n\nPython 3.15 makes UTF-8 the default on every platform ([PEP 686](https://peps.python.org/pep-0686/)), so open() reads a file the same way on every machine:\n\nsame file, saved as UTF-8:     c  a  f  é\n\n                               bytes: 63 61 66 C3 A9\n\nMac reads it with UTF-8        -> café       correct\n\nWindows reads it with UTF-8    -> café       correct\n\nTab Completion for Imports\n\nIn the interactive shell, pressing Tab after from module import now suggests the names inside the module:\n\n>>> from statistics import me<Tab>\n\nmean            median          median_grouped  median_high     median_low\n\nFor a third-party package like pandas, the first Tab offers to import it, and the second Tab shows the names:\n\n>>> from pandas import read_c<Tab>\n\n[ module not imported, press again to import it and propose attributes ]\n\n>>> from pandas import read_c<Tab>\n\nread_clipboard  read_csv\n\nTab completions are also colored by object kind now, so functions, classes, and modules are easier to tell apart. Here, classes are magenta, functions are blue, and submodules are cyan:\n\nFinal Thoughts\n\nAfter upgrading to Python 3.15, here are some things to try:\n\nIf a script takes a long time to start up, mark heavy imports like pandas, scikit-learn, and matplotlib as lazy.\n\nIf you pass a config dict between functions and don’t want it changed by accident, freeze it with frozendict.\n\nIf a job is slow, generate a Tachyon flame graph and hover over the widest bars to find the bottleneck.\n\nReferences\n\n[What’s New in Python 3.15](https://docs.python.org/3.15/whatsnew/3.15.html) (Python Software Foundation, 2026): release date and the full list of features covered here.\n\n[profiling.sampling documentation](https://docs.python.org/3.15/library/profiling.sampling.html) (Python Software Foundation, 2026): run, attach, dump, modes, and output formats.\n\nRelated Tutorials\n\n[What’s New in pandas 3.0: Expressions, Copy-on-Write, and Faster Strings](https://codecut.ai/pandas-3-whats-new/): The biggest pandas changes to know if you’re upgrading pandas along with Python.\n\n[The Hidden Cost of Python Dictionaries (And 3 Safer Alternatives)](https://codecut.ai/hidden-cost-python-dictionaries-safer-alternatives/): Covers the bugs that mutable dicts cause, which frozendict now helps prevent.\n\nThe post [Python 3.15 for Data Scientists: Lazy Imports, frozendict, and Tachyon](https://codecut.ai/python-3-15-features-data-scientists/) appeared first on [CodeCut](https://codecut.ai).", "url": "https://wpnews.pro/news/python-3-15-for-data-scientists-lazy-imports-frozendict-and-tachyon", "canonical_source": "https://codecut.ai/python-3-15-features-data-scientists/", "published_at": "2026-10-12 04:19:28+00:00", "updated_at": "2026-10-28 02:43:11.638331+00:00", "lang": "en", "topics": ["artificial-intelligence"], "entities": ["Python 3.15", "pandas", "PEP 810", "PEP 814", "frozendict", "CodeCut"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/python-3-15-for-data-scientists-lazy-imports-frozendict-and-tachyon", "markdown": "https://wpnews.pro/news/python-3-15-for-data-scientists-lazy-imports-frozendict-and-tachyon.md", "text": "https://wpnews.pro/news/python-3-15-for-data-scientists-lazy-imports-frozendict-and-tachyon.txt", "jsonld": "https://wpnews.pro/news/python-3-15-for-data-scientists-lazy-imports-frozendict-and-tachyon.jsonld"}}