{"course":{"slug":"ai-careers","title":"Working in AI","url":"https://addaly.com/learn/ai-careers","level":"beginner","track":"work"},"lesson":{"slug":"python-that-earns","title":"The employable half of Python","url":"https://addaly.com/learn/ai-careers/python-that-earns","position":23,"of":85,"minutes":9,"answer":"The paid part of Python is reading other people's code, handling files and failures, and writing a script that somebody else can run.","keyIdea":"The paid part of Python is reading other people's code, handling files and failures, and writing a script that somebody else can run."},"body":{"format":"markdown","text":"## What the job actually uses\n\nMost Python courses spend their weeks on classes, inheritance and algorithm puzzles. Most Python jobs in this field spend their days on: read some data, reshape it, call an API, write the result somewhere, and make sure it does not fall over at 03:00.\n\nThe employable list is short.\n\n- `pathlib` for files, because string paths break across operating systems.\n- `csv` and `json` from the standard library, before reaching for anything bigger.\n- `requests` or `httpx` for APIs, with a timeout on every single call.\n- `pandas` or `polars` for tabular work.\n- `argparse` so your script takes inputs instead of hard-coding them.\n- `logging` instead of `print`, because a scheduled job with `print` statements produces output nobody sees.\n- `try` / `except` naming a specific exception, never a bare `except:`.\n- Virtual environments.\n\n## The bug that quietly destroys data\n\nThis one is worth more than a chapter of syntax.\n\n```python\ndf = pd.read_csv(\"customers.csv\")\ndf[\"customer_id\"].head()\n# 0    7.0\n# 1    42.0\n```\n\nPandas inferred the column as a float because one row was empty. `007` is now `7.0`. A postcode of `01234` is now `1234`. An account number too long for a float loses its last digits silently, with no warning of any kind.\n\nThe fix is one argument, and typing it is a professional habit:\n\n```python\ndf = pd.read_csv(\n    \"customers.csv\",\n    dtype={\"customer_id\": \"string\", \"postcode\": \"string\"},\n    keep_default_na=False,\n)\n```\n\nThe mechanism: identifiers look like numbers but are not numbers. Nothing arithmetic is ever done to them, and every operation that treats them as numeric loses information. This class of bug has broken payroll runs and lost hospital records, and no marketing page about learning Python will mention it.\n\n## \"It worked in my notebook\"\n\nNotebooks keep state. Cells run in whatever order you clicked them, variables persist after you delete the cell that made them, and the thing that makes your code work may no longer exist anywhere in the file.\n\nThe discipline: **restart the kernel and run all, before you believe any result.** Do it before you show anyone anything. Half of the \"I cannot reproduce it\" conversations in data teams are this.\n\n## A script skeleton worth memorising\n\n```python\nimport argparse, logging, sys\nfrom pathlib import Path\nimport pandas as pd\n\nlogging.basicConfig(level=logging.INFO,\n                    format=\"%(asctime)s %(levelname)s %(message)s\")\n\ndef clean(df: pd.DataFrame) -> pd.DataFrame:\n    before = len(df)\n    df = df.drop_duplicates(subset=[\"order_id\"])\n    logging.info(\"dropped %d duplicate orders\", before - len(df))\n    return df\n\ndef main() -> int:\n    p = argparse.ArgumentParser()\n    p.add_argument(\"--input\", type=Path, required=True)\n    p.add_argument(\"--output\", type=Path, required=True)\n    args = p.parse_args()\n\n    df = pd.read_csv(args.input, dtype={\"order_id\": \"string\"})\n    logging.info(\"read %d rows from %s\", len(df), args.input)\n    clean(df).to_csv(args.output, index=False)\n    return 0\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n```\n\nEvery line of that is doing employable work: it takes arguments, it says what it did, it names its types, it returns an exit code a scheduler can read. A reviewer looking at this knows you have run something in production, or at least understand what production needs.\n\n## Environments, without the folklore\n\n```bash\npython -m venv .venv\nsource .venv/bin/activate      # Windows: .venv\\Scripts\\activate\npip install -r requirements.txt\n```\n\nThat is the whole thing. `uv` does the same faster if you prefer. The point is that your project declares its dependencies in a file, so a colleague, or you in six months, can run it.\n\nThe free path covers everything: plain `venv` and `pip`, VS Code or VSCodium, Colab for anything needing a GPU. The paid options people assume they need (commercial Anaconda licences, PyCharm Professional) buy convenience, not capability.\n\n## The most underrated skill: reading someone else's code\n\nYou will spend more time reading code than writing it, and almost nobody practises it deliberately. A method that works on any unfamiliar repository:\n\n1. Find the entry point: the file with `if __name__ == \"__main__\"`, or the route definitions, or whatever the README says to run.\n2. Follow exactly one path from input to output. One request, one record, one file.\n3. Ignore everything else, including the parts that look important.\n4. Change one small thing and run it. What breaks tells you what depends on what.\n\nAn hour of this on a real open-source project teaches more than a week of tutorials, and it is precisely what your first month at a job will be.\n\n## Where pandas stops\n\nPandas holds everything in memory and copies aggressively, so on an ordinary laptop it starts struggling somewhere around a few gigabytes, and the effective ceiling is lower than your RAM, because an operation may need several copies at once. That is the honest threshold. Above it, the answers are `polars`, DuckDB over Parquet files, or moving the work into a database. Knowing the number is more useful than the vague claim that pandas is slow."},"check":{"question":"A nightly script reads a CSV of member records with `pd.read_csv` and writes them to a database. Months later, staff find that members whose ID begins with a zero cannot be looked up, and a few long account numbers end in incorrect digits. The script has never raised an error. What happened?","options":[{"text":"Pandas inferred the ID column as numeric, so leading zeros were dropped and very long values lost precision: a silent conversion that produces no error, and is prevented by declaring the column as a string on read.","correct":true},{"text":"The database column is too narrow, so values were truncated on insert.","correct":false,"tempting":"Blames the destination, which would usually raise an error or truncate uniformly rather than affecting only leading zeros and long values."},{"text":"The CSV was written with the wrong encoding, corrupting some characters.","correct":false,"tempting":"An encoding fault garbles text visibly; it does not selectively remove leading zeros while leaving everything else readable."},{"text":"Duplicate member rows were silently overwritten during the load.","correct":false,"tempting":"Explains missing records in general but not the specific pattern of leading zeros disappearing and long numbers changing their last digits."}]},"prev":{"slug":"sql-is-the-gate","title":"SQL, and the six query shapes that cover the job","url":"https://addaly.com/learn/ai-careers/sql-is-the-gate"},"next":{"slug":"statistics-you-will-be-asked-about","title":"The statistics that come up, and the ones that never do","url":"https://addaly.com/learn/ai-careers/statistics-you-will-be-asked-about"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The employable half of Python', in Working in AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/ai-careers/python-that-earns (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The employable half of Python","courseTitle":"Working in AI","module":{"index":3,"slug":"the-evidence","title":"The evidence: what gets tested, and how to prove you have it"},"answer":null,"keyIdea":"The paid part of Python is reading other people’s code, handling files and failures, and writing a script that somebody else can run.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"what-the-job-actually-uses","runs":[{"text":"What the job actually uses"}]},{"t":"p","runs":[{"text":"Most Python courses spend their weeks on classes, inheritance and algorithm puzzles. Most Python jobs in this field spend their days on: read some data, reshape it, call an API, write the result somewhere, and make sure it does not fall over at 03:00."}]},{"t":"p","runs":[{"text":"The employable list is short."}]},{"t":"ul","items":[[{"text":"pathlib","code":true},{"text":" for files, because string paths break across operating systems."}],[{"text":"csv","code":true},{"text":" and "},{"text":"json","code":true},{"text":" from the standard library, before reaching for anything bigger."}],[{"text":"requests","code":true},{"text":" or "},{"text":"httpx","code":true},{"text":" for APIs, with a timeout on every single call."}],[{"text":"pandas","code":true},{"text":" or "},{"text":"polars","code":true},{"text":" for tabular work."}],[{"text":"argparse","code":true},{"text":" so your script takes inputs instead of hard-coding them."}],[{"text":"logging","code":true},{"text":" instead of "},{"text":"print","code":true},{"text":", because a scheduled job with "},{"text":"print","code":true},{"text":" statements produces output nobody sees."}],[{"text":"try","code":true},{"text":" / "},{"text":"except","code":true},{"text":" naming a specific exception, never a bare "},{"text":"except:","code":true},{"text":"."}],[{"text":"Virtual environments."}]]},{"t":"h2","id":"the-bug-that-quietly-destroys-data","runs":[{"text":"The bug that quietly destroys data"}]},{"t":"p","runs":[{"text":"This one is worth more than a chapter of syntax."}]},{"t":"code","lang":"python","label":"Python","text":"df = pd.read_csv(\"customers.csv\")\ndf[\"customer_id\"].head()\n# 0    7.0\n# 1    42.0"},{"t":"p","runs":[{"text":"Pandas inferred the column as a float because one row was empty. "},{"text":"007","code":true},{"text":" is now "},{"text":"7.0","code":true},{"text":". A postcode of "},{"text":"01234","code":true},{"text":" is now "},{"text":"1234","code":true},{"text":". An account number too long for a float loses its last digits silently, with no warning of any kind."}]},{"t":"p","runs":[{"text":"The fix is one argument, and typing it is a professional habit:"}]},{"t":"code","lang":"python","label":"Python","text":"df = pd.read_csv(\n    \"customers.csv\",\n    dtype={\"customer_id\": \"string\", \"postcode\": \"string\"},\n    keep_default_na=False,\n)"},{"t":"p","runs":[{"text":"The mechanism: identifiers look like numbers but are not numbers. Nothing arithmetic is ever done to them, and every operation that treats them as numeric loses information. This class of bug has broken payroll runs and lost hospital records, and no marketing page about learning Python will mention it."}]},{"t":"h2","id":"it-worked-in-my-notebook","runs":[{"text":"“It worked in my notebook”"}]},{"t":"p","runs":[{"text":"Notebooks keep state. Cells run in whatever order you clicked them, variables persist after you delete the cell that made them, and the thing that makes your code work may no longer exist anywhere in the file."}]},{"t":"p","runs":[{"text":"The discipline: "},{"text":"restart the kernel and run all, before you believe any result.","b":true},{"text":" Do it before you show anyone anything. Half of the “I cannot reproduce it” conversations in data teams are this."}]},{"t":"h2","id":"a-script-skeleton-worth-memorising","runs":[{"text":"A script skeleton worth memorising"}]},{"t":"code","lang":"python","label":"Python","text":"import argparse, logging, sys\nfrom pathlib import Path\nimport pandas as pd\n\nlogging.basicConfig(level=logging.INFO,\n                    format=\"%(asctime)s %(levelname)s %(message)s\")\n\ndef clean(df: pd.DataFrame) -> pd.DataFrame:\n    before = len(df)\n    df = df.drop_duplicates(subset=[\"order_id\"])\n    logging.info(\"dropped %d duplicate orders\", before - len(df))\n    return df\n\ndef main() -> int:\n    p = argparse.ArgumentParser()\n    p.add_argument(\"--input\", type=Path, required=True)\n    p.add_argument(\"--output\", type=Path, required=True)\n    args = p.parse_args()\n\n    df = pd.read_csv(args.input, dtype={\"order_id\": \"string\"})\n    logging.info(\"read %d rows from %s\", len(df), args.input)\n    clean(df).to_csv(args.output, index=False)\n    return 0\n\nif __name__ == \"__main__\":\n    sys.exit(main())"},{"t":"p","runs":[{"text":"Every line of that is doing employable work: it takes arguments, it says what it did, it names its types, it returns an exit code a scheduler can read. A reviewer looking at this knows you have run something in production, or at least understand what production needs."}]},{"t":"h2","id":"environments-without-the-folklore","runs":[{"text":"Environments, without the folklore"}]},{"t":"code","lang":"bash","label":"Bash","text":"python -m venv .venv\nsource .venv/bin/activate      # Windows: .venv\\Scripts\\activate\npip install -r requirements.txt"},{"t":"p","runs":[{"text":"That is the whole thing. "},{"text":"uv","code":true},{"text":" does the same faster if you prefer. The point is that your project declares its dependencies in a file, so a colleague, or you in six months, can run it."}]},{"t":"p","runs":[{"text":"The free path covers everything: plain "},{"text":"venv","code":true},{"text":" and "},{"text":"pip","code":true},{"text":", VS Code or VSCodium, Colab for anything needing a GPU. The paid options people assume they need (commercial Anaconda licences, PyCharm Professional) buy convenience, not capability."}]},{"t":"h2","id":"the-most-underrated-skill-reading-someone-else-s-code","runs":[{"text":"The most underrated skill: reading someone else’s code"}]},{"t":"p","runs":[{"text":"You will spend more time reading code than writing it, and almost nobody practises it deliberately. A method that works on any unfamiliar repository:"}]},{"t":"ol","start":1,"items":[[{"text":"Find the entry point: the file with "},{"text":"if __name__ == \"__main__\"","code":true},{"text":", or the route definitions, or whatever the README says to run."}],[{"text":"Follow exactly one path from input to output. One request, one record, one file."}],[{"text":"Ignore everything else, including the parts that look important."}],[{"text":"Change one small thing and run it. What breaks tells you what depends on what."}]]},{"t":"p","runs":[{"text":"An hour of this on a real open-source project teaches more than a week of tutorials, and it is precisely what your first month at a job will be."}]},{"t":"h2","id":"where-pandas-stops","runs":[{"text":"Where pandas stops"}]},{"t":"p","runs":[{"text":"Pandas holds everything in memory and copies aggressively, so on an ordinary laptop it starts struggling somewhere around a few gigabytes, and the effective ceiling is lower than your RAM, because an operation may need several copies at once. That is the honest threshold. Above it, the answers are "},{"text":"polars","code":true},{"text":", DuckDB over Parquet files, or moving the work into a database. Knowing the number is more useful than the vague claim that pandas is slow."}]}],"check":{"question":[{"text":"A nightly script reads a CSV of member records with "},{"text":"pd.read_csv","code":true},{"text":" and writes them to a database. Months later, staff find that members whose ID begins with a zero cannot be looked up, and a few long account numbers end in incorrect digits. The script has never raised an error. What happened?"}],"options":[{"text":[{"text":"Pandas inferred the ID column as numeric, so leading zeros were dropped and very long values lost precision: a silent conversion that produces no error, and is prevented by declaring the column as a string on read."}],"correct":true,"tempting":null},{"text":[{"text":"The database column is too narrow, so values were truncated on insert."}],"correct":false,"tempting":[{"text":"Blames the destination, which would usually raise an error or truncate uniformly rather than affecting only leading zeros and long values."}]},{"text":[{"text":"The CSV was written with the wrong encoding, corrupting some characters."}],"correct":false,"tempting":[{"text":"An encoding fault garbles text visibly; it does not selectively remove leading zeros while leaving everything else readable."}]},{"text":[{"text":"Duplicate member rows were silently overwritten during the load."}],"correct":false,"tempting":[{"text":"Explains missing records in general but not the specific pattern of leading zeros disappearing and long numbers changing their last digits."}]}],"order":[2,3,1,0],"why":null},"prev":{"slug":"sql-is-the-gate","title":"SQL, and the six query shapes that cover the job","minutes":9},"next":{"slug":"statistics-you-will-be-asked-about","title":"The statistics that come up, and the ones that never do","minutes":10}}}