Предположим, что у меня есть функция, которая может или взять повторяемое / итератор или неповторяемое как аргумент. С Iterability согласовывают try: iter(arg)
.
Завися, является ли вход повторяемым или нет, результат метода будет отличаться. Не, когда я хочу передать неповторяемое как повторяемый вход, легко сделать: я просто перенесу его с кортежем.
Что я делаю, когда я хочу передать повторяемое (строка, например), но хотеть, чтобы функция взяла его, как будто это неповторяемо? Например, сделайте это iter(str)
сбои.
Редактирование – мое исходное намерение:
Я хотел сделать вывод zip
функция в этом это может архивировать iterables с non-iterables. Неповторяемое было бы затем repeat
самостоятельно так же часто, как другие iterables не заканчивались.
Единственное общее решение fo меня, кажется, теперь, который я не должен проверять в general_zip
функция (из-за строковых проблем); но это вместо этого я должен буду добавить repeat
итератор к аргументу перед вызовом zip
. (Это на самом деле сохраняет меня от изобретения general_zip
функция — хотя я все еще мог бы, потому что с неповторяемым как вход это будет однозначно без дополнительного повторения.)
Чем больше я об этом думаю, тем больше кажется, что невозможно обойтись без проверки типов или передачи аргументов функции.
Однако, в зависимости от цели функции, один из способов ее обработки может быть следующим:
from itertools import repeat
func(repeat(string_iterable))
func
по-прежнему видит итерацию, но не выполняет итерацию по символам самой строки. Фактически, аргумент работает так, как если бы он не повторялся.
Специализируйтесь на нем.
def can_iter(arg):
if isinstance(arg, str):
return False
try:
...
Не проверять итеративность. Ошибочно иметь функцию, которая проверяет типы / возможности ее элементов, чтобы одна функция выполняла разные задачи. Если вы хотите делать две разные вещи, сделайте две разные функции.
Похоже, вы сами пришли к такому выводу и предоставляете согласованный 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
.
Ого! Похоже, вы хотите иметь возможность передавать итерируемые элементы как итерируемые, итерируемые элементы как не повторяющиеся, а не повторяющиеся как итерируемые, и не повторяющиеся как не повторяющиеся. Поскольку вы хотите иметь возможность обрабатывать все возможности, а компьютер (пока) не может читать мысли, вам придется указать функции, как вы хотите обрабатывать аргумент:
def foo_iterable(iterable):
...
def foo_noniterable(noniterable):
...
def foo(thing,isiterable=True):
if isiterable:
foo_iterable(thing)
else:
foo_noniterable(thing)
Apply foo к итерируемому
foo(iterable)
Применить foo к итерируемому как не повторяемому:
foo_noniterable(iterable) # or
foo(iterable, isiterable=False)
Применить foo к непередаваемому как непередаваемому:
foo_noniterable(noniterable) # or
foo(noniterable,isiterable=False)
Применить foo к непередаваемому как итерируемому:
foo((noniterable,))
PS. Я верю в небольшие функции, которые хорошо выполняют одну работу. Их легче отлаживать и тестировать. В общем, я бы посоветовал избегать монолитных функций, которые ведут себя по-разному в зависимости от типа.Да, это накладывает небольшое дополнительное бремя на разработчика, вызывая именно ту функцию, которая предназначена, но я думаю, что преимущества с точки зрения отладки и модульного тестирования с лихвой компенсируют это.
Итак, один из способов сообщить функции, как вы хотели бы обращаться с ее аргументами, - это иметь разумные значения по умолчанию (заставляя функцию обрабатывать все по исходному типу по умолчанию), при этом имея возможность указать любые настройки, которые вам нравятся, с комфортом ( т.е. с короткой и отсутствующей по умолчанию строкой 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
Расширяя эту функцию (нахождение длины самой длинной итерации и т. д.), можно построить 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»
как неповторяемый). Этот подход, конечно, можно комбинировать с вышеупомянутым «тумблерным» подходом.