{"record":{"id":"57f6628d3d3cc3d5","repo":"pola-rs/polars","slug":"cannot-specify-both-columns-and-read-options-c","errorCode":null,"errorMessage":"cannot specify both `columns` and `read_options[\"columns\"]`","messagePattern":"cannot specify both `columns` and `read_options\\[\"columns\"\\]`","errorType":"exception","errorClass":"ParameterCollisionError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/io/spreadsheet/functions.py","lineNumber":757,"sourceCode":"            msg = 'cannot specify both `columns` and `read_options[\"use_columns\"]`'\n            raise ParameterCollisionError(msg)\n        elif read_options.get(\"header_row\") is not None and has_header is False:\n            msg = 'the values of `has_header` and `read_options[\"header_row\"]` are not compatible'\n            raise ParameterCollisionError(msg)\n        elif (\"schema_sample_rows\" in read_options) and (\n            infer_schema_length != N_INFER_DEFAULT\n        ):\n            msg = 'cannot specify both `infer_schema_length` and `read_options[\"schema_sample_rows\"]`'\n            raise ParameterCollisionError(msg)\n\n        read_options[\"schema_sample_rows\"] = infer_schema_length\n        if has_header is False and \"header_row\" not in read_options:\n            read_options[\"header_row\"] = None\n\n    elif engine == \"xlsx2csv\":\n        if (\"columns\" in read_options) and columns:\n            msg = 'cannot specify both `columns` and `read_options[\"columns\"]`'\n            raise ParameterCollisionError(msg)\n        elif (\n            \"has_header\" in read_options\n            and read_options[\"has_header\"] is not has_header\n        ):\n            msg = 'the values of `has_header` and `read_options[\"has_header\"]` are not compatible'\n            raise ParameterCollisionError(msg)\n        elif (\"infer_schema_length\" in read_options) and (\n            infer_schema_length != N_INFER_DEFAULT\n        ):\n            msg = 'cannot specify both `infer_schema_length` and `read_options[\"infer_schema_length\"]`'\n            raise ParameterCollisionError(msg)\n\n        read_options[\"infer_schema_length\"] = infer_schema_length\n        if \"has_header\" not in read_options:\n            read_options[\"has_header\"] = has_header\n    else:\n        read_options[\"infer_schema_length\"] = infer_schema_length\n        read_options[\"has_header\"] = has_header","sourceCodeStart":739,"sourceCodeEnd":775,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/io/spreadsheet/functions.py#L739-L775","documentation":"Raised by polars.read_excel (engine='xlsx2csv') in _get_read_options when column selection is supplied twice: once via the top-level `columns` parameter and once via the `read_options['columns']` key (which is forwarded to the internal read_csv call). polars refuses to guess which selection wins, so it raises ParameterCollisionError (a PolarsError subclass, NOT a ValueError). The xlsx2csv engine converts the sheet to CSV and reads it with read_csv, which is why csv-level keys like 'columns' can appear in read_options at all.","triggerScenarios":"pl.read_excel('f.xlsx', engine='xlsx2csv', columns=['a','b'], read_options={'columns': ['a','b'], ...}) — i.e. a truthy top-level columns argument AND the key 'columns' present in the read_options dict. Fires before any file parsing starts.","commonSituations":"Copy-pasting a batch of read_csv kwargs into read_options while also using the polars-level `columns` parameter; migrating older code that selected columns via csv options; wrapper functions that merge a user dict into read_options and also expose `columns`.","solutions":["Delete the 'columns' key from read_options and keep only the top-level columns parameter","Alternatively drop the top-level columns argument and keep read_options['columns'] (less idiomatic)","Audit read_options for other duplicated polars-level params (has_header, infer_schema_length) while you are there"],"exampleFix":"# before\npl.read_excel('f.xlsx', engine='xlsx2csv', columns=['a', 'b'], read_options={'columns': ['a', 'b'], 'truncate_ragged_lines': True})\n\n# after\npl.read_excel('f.xlsx', engine='xlsx2csv', columns=['a', 'b'], read_options={'truncate_ragged_lines': True})","handlingStrategy":"validation","validationCode":"def check_xlsx2csv_columns(columns, read_options):\n    if columns and 'columns' in (read_options or {}):\n        raise ValueError(\"pass column selection either via `columns` or read_options['columns'], not both\")\n\ncheck_xlsx2csv_columns(columns, read_options)\ndf = pl.read_excel(src, engine='xlsx2csv', columns=columns, read_options=read_options)","typeGuard":null,"tryCatchPattern":"from polars.exceptions import ParameterCollisionError\ntry:\n    df = pl.read_excel(src, engine='xlsx2csv', columns=cols, read_options=opts)\nexcept ParameterCollisionError as e:\n    # ParameterCollisionError is a PolarsError, NOT a ValueError\n    raise ValueError(f'bad read_options for xlsx2csv: {e}') from e","preventionTips":["Reserve read_options for engine/csv-specific keys; express polars-level concerns (columns, has_header, infer_schema_length) only via top-level parameters","Centralize read_options construction in one helper that asserts it contains no polars-level keys","Remember ParameterCollisionError subclasses PolarsError, so except ValueError will not catch it"],"tags":["polars","excel","xlsx2csv","parameter-collision","columns","validation"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}