SQLAlchemy has a general philosophy of fn(*args) vs fn(list):
1. if the sequence of items represents **database data**, we use a **list** or other inline sequence. E.g. in_():
column.in_([1, 2, 3])
2. if the sequence of items represents **SQL structure**, we use a variable length *Args. E.g. Table:
Table(name, metadata, *cols_and_other_constraints)
and_(), or(), etc:
and_(*sequence_of_expressions(
ORM query:
session.query(*sequence_of_entities_expressions_etc)
select() should work the same as all these other APIs and in particular it's now largely cross-compatible with ORM query as well.
The reason select() has always accepted a a list is because the very ancient and long de-emphasized API for select() looked like this:
stmt= select({table.c.col1, table.c.ol2], table.c.col1 == "some_expression", order_by=table.c.col1)
that is, the "generative" API for select() that is very normal now did not exist. There was no select().where().order_by(), that was all added years after SQLAlchemy's first releases. All of those kwargs are also deprecated as they are very ancient legacy code and we'd like SQLAlchemy's API to be clean and consistent and not allowing of many variations of the same thing, as this makes the whole system easier to use and understand.
So the use of a list in select() is based on long ago deemphasized API that SQLAlchemy 2.0 seeks to finally remove. As this API is one of the most complicated to provide a backwards-compatibility path for, it's taken a very long time for us to get there. But here we are.