2010-03-20 7 views
5

Предположим, у меня есть функция, которая может либо взять итеративный/итератор, либо неистребимый в качестве аргумента. Итеративность проверяется с помощью try: iter(arg).Метод Python для удаления итерации

В зависимости от того, является ли вход итерабельным или нет, результат метода будет другим. Не тогда, когда я хочу передать неистребимый как итерируемый ввод, это легко сделать: я просто оберну его кортежем.

Что делать, если я хочу передать итерабельность (например, строку), но хотите, чтобы функция воспринимала ее так, как будто она не итерируема? Например. сделать это iter(str) не удается.

Edit - мое первоначальное намерение:

Я хотел обобщить функцию zip в том, что она может пронестись итерируемой с не итерируемыми. Неистребимый будет тогда repeat сам так часто, как другие итерации еще не закончены.

Единственное общее решение для меня теперь кажется, что я не должен проверять внутри функции general_zip (из-за проблем с строкой); но вместо этого я должен добавить итератор repeat к аргументу до, вызывающего zip. (Это фактически спасает меня от изобретения функции general_zip - хотя я все еще могу, потому что с неизменяемым в качестве ввода он будет недвусмысленным без дополнительного повтора.)

+3

Путь Python предназначен для того, чтобы вызывающий мог быть явным и преобразовать неизменяемый в итерируемый. 'zip (my_list, itertools.repeat (42))' Это то же самое, что и писать '42 + int ('100')', если вы хотите добавить int и строку. Добавление магических преобразований приводит к угадыванию и путанице. – 2010-03-20 15:31:08

+0

Да, правильно. Но мне нужно вызвать эту функцию много раз, и тогда мне придется делать проверку каждый раз, прежде чем называть ее. Это выглядит немного лишним. - Итак, причина, по которой мне нужна эта функция, является довольно узкой и четко определенной, однако я бы хотел, чтобы потенциальные возможности этой функции были максимально возможными. – Debilski

ответ

3

Чем больше я думаю об этом, кажется, что это невозможно обойтись без проверки типа или передачи полномочий функции.

Однако, в зависимости от намерения функции, один из способов справиться с этим может быть:

from itertools import repeat 
func(repeat(string_iterable)) 

func все еще видит итератор, но он не будет перебирать Charaters самой строки. И эффективно, аргумент работает так, как если бы он был неизменным.

+0

'repeat (string_iterable)' будет бесконечно возвращать строку. Вы имели в виду '[string_iterable]' (он вернет строку только один раз)? – jfs

+0

Нет, для моей проблемы [string_iterable] было бы неправильным решением. Аналогия должна быть той же точки в одном измерении, которая при ее расширении в двух измерениях соответствует целой строке точек, а не только одной. – Debilski

0

Специализируйте его.

def can_iter(arg): 
    if isinstance(arg, str): 
    return False 
    try: 
    ... 
+0

Но это означает, что мне нужно решить внутри функции и не может принять новое решение из-за случая. – Debilski

2

Whoo! Похоже, вы хотите иметь возможность передавать итерации в виде iterables, iterables как noniterables, noniterables в качестве iterables и noniterables как noniterables. Так как вы хотите, чтобы иметь возможность обрабатывать каждую возможность, и компьютер не может (пока) читать мысли, вы будете иметь, чтобы указать функции, как вы хотите, чтобы аргумент, который будет обрабатываться:

def foo_iterable(iterable): 
    ... 
def foo_noniterable(noniterable): 
    ... 

def foo(thing,isiterable=True): 
    if isiterable: 
     foo_iterable(thing) 
    else: 
     foo_noniterable(thing) 

Применить Foo к Iterable

foo(iterable) 

Применить Foo к итератору как noniterable:

foo_noniterable(iterable)  # or 
foo(iterable, isiterable=False) 

Применить Foo к noniterable как noniterable:

foo_noniterable(noniterable)  # or 
foo(noniterable,isiterable=False) 

Применить Foo к noniterable в качестве итератора:

foo((noniterable,)) 

PS. Я верю в небольшие функции, которые хорошо выполняют одну работу. Их легче отлаживать и тестировать по отдельности. В общем, я бы посоветовал избежать монолитных функций, которые ведут себя по-разному в зависимости от типа. Да, это накладывает небольшую дополнительную нагрузку на разработчика, чтобы назвать именно ту функцию, которая предназначена, но я думаю, что преимущества в плане отладки и модульного тестирования более чем компенсируют это.

+0

Проблема состоит в том, что функция может иметь несколько аргументов; тогда будет немного сложнее. – Debilski

+0

@Debilski: Почему бы не добавить несколько аргументов в 'foo'? Может быть, я не знаю достаточно о вашей ситуации. Почему это сложно? – unutbu

+0

Не знаю. Похоже, что это закончится как таковое: 'foo ([1,2,3], [1,2,3]," abc ", isiterable1 = True, isiterable2 = True, isiterable3 = False)'. Или с большим количеством аргументов. Я думаю, что ваше решение будет в порядке с одним или двумя фиксированными аргументами; моя ситуация в том, что я хотел бы, чтобы это было более общим. (В противном случае, я думаю, что мне все равно не хотелось бы спрашивать и просто сделал бы это более или менее, как вы предлагали.) – Debilski

0

Ну, один из способов рассказать о том, как вы хотите обработать его аргументы, - иметь разумные значения по умолчанию (при этом функция по умолчанию обрабатывает все по своему первоначальному типу), имея возможность указать любые настройки, которые вам нравятся с комфортом (т.е. с коротким и рассеянным по-умолчанию fmt строки), как:

def smart_func(*args, **kw): 
    """If 'kw' contains an 'fmt' parameter, 
    it must be a list containing positions of arguments, 
    that should be treated as if they were of opposite 'kind' 
    (i.e. iterables will be treated as non-iterables and vise-versa) 

    The 'kind' of a positional argument (i.e. whether it as an iterable) 
    is inferred by trying to call 'iter()' on the argument. 
    """ 

    fmt = kw.get('fmt', []) 

    def is_iter(it): 
     try: 
      iter(it) 
      return True 
     except TypeError: 
      return False 

    for i,arg in enumerate(args): 
     arg_is_iterable = is_iter(arg) 
     treat_arg_as_iterable = ((not arg_is_iterable) 
           if (i in fmt) else arg_is_iterable) 
     print arg, arg_is_iterable, treat_arg_as_iterable 

Это дает:

>>> smart_func() 
>>> smart_func(1, 2, []) 
1 False False 
2 False False 
[] True True 
>>> smart_func(1, 2, [], fmt=[]) 
1 False False 
2 False False 
[] True True 
>>> smart_func(1, 2, [], fmt=[0]) 
1 False True 
2 False False 
[] True True 
>>> smart_func(1, 2, [], fmt=[0,2]) 
1 False True 
2 False False 
[] True False 

Расширение этой функции (нахождение длины самого длинного Iterable, и т.д.), один может построить smart-zip Вы говорите.

[Ps] Другим способом будет вызывать функцию следующим образом:

smart_func(s='abc', 1, arr=[0,1], [1,2], fmt={'s':'non-iter','some_arr':'iter'}) 

и есть функция совпадает с именами аргументов вы из комплекта поставки ('s' и 'arr', к сведению, Есть нет таких имен в сигнатуре функций, как это указано выше) на 'fmt' «Тип-подсказки» (то есть 'iter' делает аргумент, рассматриваемый как итерируемый, и 'non-iter' как неистребимый). Разумеется, этот подход можно сочетать с вышеупомянутым «переключающим» типом.

+0

Нет причин использовать вложенные функции там. Вложенные функции полезны для создания замыканий, но использование их для определения постоянных функций локально глупо. –

+1

@ Майк Грэм. Причина заключается в выполнении контракта функции (указанной в его документе btw). Это правда, что рассматриваемая функция может быть перемещена за пределы (например, чтобы сделать ее повторно используемой), поскольку она фактически не зависит от локальных аргументов. Но неужели это глупо ... ну, пусть каждый выбирает это для себя, сэр :) – mlvljr

+0

Ну, теперь я вижу, что 'smart_func', возможно, слишком умный [для продолжения] :)) – mlvljr

0

Не проверяйте возможность итерации. Ошибочно иметь функцию, проверяющую элементы ее элементов/возможностей, чтобы выполнить одну функцию различных задач. Если вы хотите сделать две разные вещи, выполните две разные функции.

Это звучит, как вы пришли к такому выводу себя и обеспечение последовательного API, где вы делаете

from itertools import repeat 
zip([1, 2, 3], repeat(5), "bar") 

Обратите внимание, что это почти всегда бесполезно делать это, так как вы можете просто сделать

five = 5 
for number, letter in zip([1, 2, 3], "bar") 
    # Just use five here since it never changes 

Если вы конечно не кормите это чем-то, что уже использует zip.

+0

Вход, который дает мне пять 'также мог бы быть списком. И это происходит в нескольких точках моего кода, поэтому я не хочу проверять это все время, прежде чем я вызову функцию zipping, но вместо этого сделаю проверку внутри. Моя проблема заключалась в том, как бороться с краевыми случаями. – Debilski

+0

@Debilski Если вы хотите, чтобы ваша функция была «расфасована» с помощью итеративной/непереходной логики проверки, а также для обеспечения возможности «переопределять»/заменять способ обработки аргумента, почему бы просто не использовать простой (и пустой default) 'format' specifier? – mlvljr

+0

@ Дебильски, верно, конечно. И, очевидно, вы открыли способ передать то, что вам действительно нужно - итерабельность, которая дает одинаковое значение снова и снова. –