1

map入力シーケンスの型を保持するような関数 を実装したいと思います。map保存しません:

map(str, (8, 9))  # input is a tuple
=> ['8', '9']     # output is a list

私が思いついた1つの方法はこれです:

def map2(f, seq):
   return type(seq)( f(x) for x in seq )

map2(str, (1,2))
=> ('1', '2')
map2(str, [3,4])
=> ['3', '4']
map2(str, deque([5,6]))
=> deque(['5', '6'])

seqただし、イテレータ/ジェネレータの 場合、これは機能しません。imapこの場合に機能します。

だから私の質問は:

  1. map2リスト、タプル、および他の多くをサポートする を実装するより良い方法はありますか?
  2. map2ジェネレーターもサポートするように拡張するエレガントな方法はありimapますか? 明らかに、私は避けたい:try: return map2(...) except TypeError: return imap(...)

私がそのようなものを探している理由は、戻り値を型 X から Y に変換する関数デコレータを書いているからです。元の関数がシーケンスを返す場合 (シーケンスはリストのみであると仮定しましょう。タプル、またはジェネレータ)、私はそれが X のシーケンスであると仮定し、対応する Y のシーケンスに変換したいと考えています (シーケンスの型を保持しながら)。

お気づきかもしれませんが、私は python 2.7 を使用していますが、python 3 にも関心があります。

4

3 に答える 3

7

あなたの形式主義もどちらにも当てはまりませんmap(str,'12')

最終的に、イテラブルの型がコンストラクター/イニシャライザーで実際に取る引数がわからないため、一般的にこれを行う方法はありません。imapまた、ジェネレーターと同じタイプを提供しないことに注意してください。

>>> type(x for x in range(10))
<type 'generator'>
>>> type(imap(str,range(10)))
<type 'itertools.imap'>
>>> isinstance((x for x in range(10)),type(imap(str,range(10))))
False

「確かに python のイントロスペクションで、イニシャライザへの引数を調べることができた」と自分で考えているかもしれません-そして、あなたは正しいでしょう! ただし、イニシャライザに渡される引数の数とその名前がわかっている場合でも、実際に何を渡すべきかについての情報を取得することはできません。ドキュメント文字列からそれを理解するために何らかの機械学習アルゴリズムを書くことができると思います...しかし、それはこの質問の範囲をはるかに超えていると思います(そして、作者がうまく行動し、最初から良いドキュメント文字列を作成していると仮定しています)。

于 2013-04-30T20:50:26.317 に答える
1

まず、type(seq)( f(x) for x in seq )本当にただtype(seq)(imap(f, seq))です。なぜそれを使用しないのですか?

第二に、あなたがやろうとしていることは、一般的に意味をなさない. シーケンスだけでなく、任意のiterablemapを取ります。違いは、基本的に、シーケンスにはと があり、ランダムにアクセスできることです。len

X 型の iterable を Y 型の値から を呼び出して構築できるという規則はありませんtype(X)(y_iter)実際、これは一般にシーケンスには当てはまりますが、それが当てはまる他の例はほとんどありません。

いくつかの特別なタイプを特別に処理したい場合は、次のようにできます。

def map2(f, seq):
    it = imap(f, seq)
    if isinstance(seq, (tuple, list)):
        return type(seq)(it)
    else:
        return it

または、すべてのシーケンスがこの方法で構築できると仮定したい場合 (これはほとんどの組み込みシーケンスに当てはまりますが、たとえばxrange— シーケンスとして設計されていないがプロトコルを満たしていることを考慮してください — そしてもちろん、組み込まれている以上の保証はありません):

def map2(f, seq):
    it = imap(f, seq)
    try:
        len(seq)
    except:
        return it
    else:
        return type(seq)(it)

イテラブルから構築できるイテラブル型はすべてシーケンスであると想定できますが(質問で提案したように)...しかし、これは利点よりも誤検知につながる可能性が高いため、そうしません。len繰り返しますが、これはシーケンスであることの定義の一部ですが、「イテレータから構築可能」はそうではなく、イテレータを指定するとまったく異なることを行う完全に合理的なイテラブル型が存在することを思い出してください。

あなたが何をしようと、それはハックになるでしょう。なぜなら、その意図はハックであり、Python 開発者の明確な設計上の希望に反するからです。イテレータ/イテラブル プロトコルの要点は、イテラブルの型をできるだけ気にしないことです。そのため、Python 3.x はさらに進んで、 や などのリストベースの関数を代わりに反復子ベースの関数に置き換えましmapfilter


では、これらの変換の 1 つをどのようにデコレータに変換するのでしょうか?

imapまず、デコレータ ビットを飛ばして、 のような関数を受け取り、この変換が適用された同等の関数を返す高階関数を書きましょう。

def sequify(func):
    def wrapped(f, seq):
        it = func(f, seq)
        try:
            len(seq)
        except:
            return it
        else:
            return type(seq)(it)
    return wrapped

そう:

>>> seqmap = sequify(itertools.imap)
>>> seqmap(int, (1.2, 2.3))
(1, 2)
>>> sequify(itertools.ifilter)(lambda x: x>0, (-2, -1, 0, 1, 2))
(1, 2)

では、これをどのようにデコレータに変換するのでしょうか? まあ、すでに関数を返す関数デコレータです。おそらく追加したいでしょうがfunctools.wraps(非デコレータの場合でも追加したいかもしれませんが)、それが唯一の変更点です。たとえば、imap のように動作するジェネレーター、またはイテレーターを返す関数を作成し、自動的に seqmap のような関数に変換することができます。

@sequify
def map_and_discard_none(func, it):
    for elem in imap(func, it):
        if elem is not None:
            yield elem

今:

>>> map_and_discard_none(lambda x: x*2 if x else x, (1, 2, None))
(2, 4)

もちろん、これは - のような構文を持つ関数に対してのみ機能しmapます。つまり、関数と iterable を取ります。(まあ、さまざまな種類の間違った型を取る関数に対して誤って機能することがあります。たとえば、呼び出すことができsequify(itertools.count(10, 5))、それがシーケンスではないことを正常に検出し5、イテレータをそのまま返すだけです。) より一般的にするには、次のようにします。次のようなことができます:

def sequify(func, type_arg=1):
    def wrapped(*args, **kwargs):
        it = func(f, seq)
        try:
            len(args[type_arg])
        except:
            return it
        else:
            return type(seq)(it)
    return wrapped

そして今、あなたは好きなものに夢中になることができsequify(itertools.combinations, 0)ます。この場合、それを便利なデコレータにするために、おそらくさらに一歩進んでください:

def sequify(type_arg=1):
    def wrapper(func):
        def wrapped(*args, **kwargs):
            it = func(f, seq)
            try:
                len(args[type_arg])
            except:
                return it
            else:
                return type(seq)(it)
        return wrapped
    return wrapper

だからあなたはこれを行うことができます:

@sequify(3)
def my_silly_function(pred, defval, extrastuff, main_iterable, other_iterable):
于 2013-04-30T21:11:27.003 に答える
1

あなたの質問は次のように要約されます: シーケンス (Python ドキュメントが敷設した同じシーケンスではなく、反復をサポートする任意の Python オブジェクトを意味するようです) と変換が与えられた場合、変換を各要素に適用して作成する一般的な方法はありますか?まったく同じタイプの新しいシーケンス?

答えはノーだ。iterable 型が iterable からの新しいインスタンスの作成をサポートするという保証はありません。一部のオブジェクトは、コンストラクターでこれを本質的にサポートしています。しない人もいます。反復可能な型は、反対の操作をサポートすることを保証しません。初期化ケースの引数として単純なイテラブルでは機能しないことがわかっているすべてのタイプを特別なケースにする必要があります。

于 2013-04-30T21:52:33.517 に答える