How do you override the result of unpacking syntax *obj
and **obj
?
For example, can you somehow create an object thing
which behaves like this:
>>> [*thing]
['a', 'b', 'c']
>>> [x for x in thing]
['d', 'e', 'f']
>>> {**thing}
{'hello world': 'I am a potato!!'}
Note: the iteration via __iter__
("for x in thing") returns different elements from the *splat unpack.
I had a look inoperator.mul
and operator.pow
, but those functions only concern usages with two operands, like a*b
and a**b
, and seem unrelated to splat operations.
*
iterates over an object and uses its elements as arguments.**
iterates over an object'skeys
and uses__getitem__
(equivalent to bracket notation) to fetch key-value pairs. To customize*
and**
, simply make your object an iterable or mapping:If you want
*
and**
to do something besides what's described above, you can't. I don't have a documentation reference for that statement (since it's easier to find documentation for "you can do this" than "you can't do this"), but I have a source quote. The bytecode interpreter loop inPyEval_EvalFrameEx
callsext_do_call
to implement function calls with*
or**
arguments.ext_do_call
contains the following code:which, if the
**
argument is not a dict, creates a dict and performs an ordinaryupdate
to initialize it from the keyword arguments (except thatPyDict_Update
won't accept a list of key-value pairs). Thus, you can't customize**
separately from implementing the mapping protocol.Similarly, for
*
arguments,ext_do_call
performswhich is equivalent to
tuple(args)
. Thus, you can't customize*
separately from ordinary iteration.It'd be horribly confusing if
f(*thing)
andf(*iter(thing))
did different things. In any case,*
and**
are part of the function call syntax, not separate operators, so customizing them (if possible) would be the callable's job, not the argument's. I suppose there could be use cases for allowing the callable to customize them, perhaps to passdict
subclasses likedefaultdict
through...I did succeed in making an object that behaves how I described in my question, but I really had to cheat. So just posting this here for fun, really -
The iterator protocol is satisfied by a generator object returned from
__iter__
(note that aThing()
instance itself is not an iterator, though it is iterable). The mapping protocol is satisfied by the presence ofkeys()
and__getitem__
. Yet, in case it wasn't already obvious, you can't call*thing
twice in a row and have it unpacka,b,c
twice in a row - so it's not really overriding splat like it pretends to be doing.