Измените то, что операторы *splat и **splatty-splat делают с моим объектом
Как вы переопределяете результат распаковки синтаксиса *obj
а также **obj
?
Например, можете ли вы как-то создать объект thing
который ведет себя так:
>>> [*thing]
['a', 'b', 'c']
>>> [x for x in thing]
['d', 'e', 'f']
>>> {**thing}
{'hello world': 'I am a potato!!'}
Примечание: итерация через__iter__
("for x in thing") возвращает различные элементы из распаковки *splat.
Я заглянул вoperator.mul
а также operator.pow
, но эти функции касаются только использования с двумя операндами, как a*b
а также a**b
и, похоже, не связаны с операциями сплат.
2 ответа
*
перебирает объект и использует его элементы в качестве аргументов. **
перебирает объект keys
и использует __getitem__
(эквивалентно скобочной записи) для получения пар ключ-значение. Чтобы настроить *
а также **
просто сделайте ваш объект итеративным или отображающим:
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
Если ты хочешь *
а также **
сделать что-то кроме того, что описано выше, вы не можете. У меня нет ссылки на документацию для этого утверждения (поскольку легче найти документацию для "вы можете сделать это", чем "вы не можете сделать это"), но у меня есть цитата из источника. Цикл интерпретатора байт-кода в PyEval_EvalFrameEx
звонки ext_do_call
реализовать вызовы функций с *
или же **
аргументы. ext_do_call
содержит следующий код:
if (!PyDict_Check(kwdict)) {
PyObject *d;
d = PyDict_New();
if (d == NULL)
goto ext_call_fail;
if (PyDict_Update(d, kwdict) != 0) {
который, если **
аргумент не является диктатом, создает диктат и выполняет обычный update
инициализировать его из аргументов ключевого слова (за исключением того, что PyDict_Update
не будет принимать список пар ключ-значение). Таким образом, вы не можете настроить **
отдельно от реализации протокола отображения.
Точно так же для *
аргументы, ext_do_call
выполняет
if (!PyTuple_Check(stararg)) {
PyObject *t = NULL;
t = PySequence_Tuple(stararg);
что эквивалентно tuple(args)
, Таким образом, вы не можете настроить *
отдельно от обычной итерации.
Было бы ужасно запутанным, если f(*thing)
а также f(*iter(thing))
делал разные вещи. В любом случае, *
а также **
являются частью синтаксиса вызова функции, а не отдельными операторами, поэтому их настройка (если это возможно) будет задачей вызываемого объекта, а не аргумента. Я полагаю, что могут быть случаи использования для разрешения настраиваемым вызываемым объектам, возможно, для передачи dict
подклассы как defaultdict
через...
Мне удалось создать объект, который ведет себя так, как я описал в своем вопросе, но мне действительно пришлось обмануть. Так что просто опубликовать это здесь для удовольствия, на самом деле -
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']
Протокол итератора удовлетворяется объектом-генератором, возвращаемым из __iter__
(обратите внимание, что Thing()
Сам экземпляр не является итератором, хотя он итеративен). Протокол отображения удовлетворяется наличием keys()
а также __getitem__
, Тем не менее, если это не было очевидно, вы не можете позвонить *thing
дважды подряд и распакуйте a,b,c
два раза подряд - так что на самом деле это не слишком сильный сплат, как будто он притворяется.