Измените то, что операторы *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 два раза подряд - так что на самом деле это не слишком сильный сплат, как будто он притворяется.

Другие вопросы по тегам