Prefer the 1st form, for three reasons.
- You're not shadowing the
type
builtin. (Trivial, could use alternate spelling type_
)
- More convenient for the caller, and for folks reading the calling code.
- Those three things go together. Better to show that, with the representation.
When we speak of (model, type, version)
,
they could be nearly anything.
There's no clear relationship among them,
and no name to hang documentation upon.
OTOH the object may have well-understood constraints,
perhaps "model is never Edsel when version > 3".
We can consult the documentation,
and the implementation,
to understand the class invariants.
Sometimes mutability is a concern.
That is, a caller might have passed in an
object with foo(test)
, and then we're
worried that library routine foo
might possibly have
changed model "Colt" to "Bronco".
Often the docs, implicit or explicit,
will make clear that such mutations
are out of bounds, they will not happen.
To make things very obvious with
minimal documentation burden, consider
using a named tuple
for those three fields in the example.
need to worry in Python about speed and memory passing class all the time?
No.
Python is about clarity of communicating a technical
idea to other humans. It is not about speed.
Recall Knuth's advice. If speed was a principal
concern, you would have already used
cProfile
to identify the hot spots that should be
implemented in e.g. Rust, cython, or C++.
Usually that only becomes important when you
notice you're often looping more than a thousand
or a million times.
Use dis.dis()
to disassemble your two functions.
Notice that caller1 pushed a single reference
to test
, while caller2 spent more time and
more stack memory pushing three references.
Down in the target code, we still need to
chase three references, so that's mostly a wash.
If you pass an object with a dozen attributes,
of which just three will be used, that's no
burden on the bytecode interpreter, the other
nine are simply never touched.
It can be an intellectual burden on an engineer
maintaining the code, who might need to reason
about those nine and dismiss them as not a concern.
Another concern that a paranoid caller might have
about called library code relates to references.
Typically we expect the called routine will not
permanently hold a reference (or weakref) on
the passed test
object, nor on attributes
such as test.version
or test.version.history_dict
.
If the library routine will store a reference for a
long time, or pass a reference to someone that will
store it, well, that's worth documenting.
Caller will want to understand memory consumption,
leaks, and object lifetime.