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.
A parameter with the splat operator converts the arguments to an array within a method. The arguments are passed in the same order in which they are specified when a method is called. A method can't have two parameters with splat operator.
Single *Splat It can do things like combine arrays, turn hashes and strings into arrays, or pull items out of an array!
What is the Splat Operator? The * (or splat) operator allows a method to take an arbitrary number of arguments and is perfect for situations when you would not know in advance how many arguments will be passed in to a method. Here's an example: def name_greeting(*names) names. each do |name| puts "Hello, #{name}!"
*
iterates over an object and uses its elements as arguments. **
iterates over an object's keys
and uses __getitem__
(equivalent to bracket notation) to fetch key-value pairs. To customize *
, simply make your object iterable, and to customize **
, make your object a mapping:
class MyIterable(object):
def __iter__(self):
return iter([1, 2, 3])
class MyMapping(collections.Mapping):
def __iter__(self):
return iter('123')
def __getitem__(self, item):
return int(item)
def __len__(self):
return 3
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 in PyEval_EvalFrameEx
calls ext_do_call
to implement function calls with *
or **
arguments. ext_do_call
contains the following code:
if (!PyDict_Check(kwdict)) {
PyObject *d;
d = PyDict_New();
if (d == NULL)
goto ext_call_fail;
if (PyDict_Update(d, kwdict) != 0) {
which, if the **
argument is not a dict, creates a dict and performs an ordinary update
to initialize it from the keyword arguments (except that PyDict_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
performs
if (!PyTuple_Check(stararg)) {
PyObject *t = NULL;
t = PySequence_Tuple(stararg);
which is equivalent to tuple(args)
. Thus, you can't customize *
separately from ordinary iteration.
It'd be horribly confusing if f(*thing)
and f(*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 pass dict
subclasses like defaultdict
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 -
class Thing:
def __init__(self):
self.mode = 'abc'
def __iter__(self):
if self.mode == 'abc':
yield 'a'
yield 'b'
yield 'c'
self.mode = 'def'
else:
yield 'd'
yield 'e'
yield 'f'
self.mode = 'abc'
def __getitem__(self, item):
return 'I am a potato!!'
def keys(self):
return ['hello world']
The iterator protocol is satisfied by a generator object returned from __iter__
(note that a Thing()
instance itself is not an iterator, though it is iterable). The mapping protocol is satisfied by the presence of keys()
and __getitem__
. Yet, in case it wasn't already obvious, you can't call *thing
twice in a row and have it unpack a,b,c
twice in a row - so it's not really overriding splat like it pretends to be doing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With