いろいろ備忘録日記

主に .NET とか Java とか Python絡みのメモを公開しています。

Pythonメモ-47 (結果の型を特定の値の型と一致させる) (python3, type, coerce)

概要

Fluent Python ―Pythonicな思考とコーディング手法

Fluent Python ―Pythonicな思考とコーディング手法

  • 作者: Luciano Ramalho,豊沢聡,桑井博之,梶原玲子
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2017/10/07
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

に書いてあった小ネタ。なるほどって思ったので忘れない内にメモ。

2つの数値を受け取る処理があって、中で処理して結果を返すときに

基本 python に任せておけば、いいようにやってくれるのですが

型を揃えたいときがあります。

たとえば、上に挙げた書籍「Fluent Python」のサンプルコードほぼパクリですが

以下のようなジェネレータ関数があるとして

def my_range1(begin, step, end=None):
    result = begin
    forever = end is None

    while forever or result < end:
        yield result
        result += step

以下のように使うと

    gen1 = my_range1(1, 2, 5)
    for x in gen1:
        print(f'gen1={x}({type(x)})')

当然結果は

gen1=1(<class 'int'>)
gen1=3(<class 'int'>)

となります。

でも、以下のように呼び出すと

    gen1 = my_range1(1, 2.0, 5.0)
    for x in gen1:
        print(f'gen1={x}({type(x)})')

結果は

gen1=1(<class 'int'>)
gen1=3.0(<class 'float'>)

ってなります。これは、beginが int で、step を float で指定しているため

最初の yield では、begin の値がそのまま生成されるため、int で、次からは step を加算した値が生成されるため float となります。

このままでいい場合もありますし、最初の yield の値から float で揃えたい場合もあります。

で、上の本に記載されていたのを使うと以下のようになります。

def my_range2(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None

    while forever or result < end:
        yield result
        result += step

変更点は、以下の部分です。

result = type(begin + step)(begin)

これ、最初「何やってるんだろう?」って思ったのですが、要は 一旦足してみてその結果の型で結果の初期値を生成ってやってるんですね。

なるほどぉって思いました。

で、このようにすると、結果が以下のようになります。

    gen2 = my_range2(1, 2, 5)
    for x in gen2:
        print(f'gen2={x}({type(x)})')

結果は

gen2=1(<class 'int'>)
gen2=3(<class 'int'>)

となり

    gen2 = my_range2(1, 2.0, 5.0)
    for x in gen2:
        print(f'gen2={x}({type(x)})')

gen2=1.0(<class 'float'>)
gen2=3.0(<class 'float'>)

と、一回目の yield から float になります。

ついでに、decimal.Decimal つかった場合も

    gen1 = my_range1(1, Decimal(2.0), Decimal(5.0))
    for x in gen1:
        print(f'gen1={x}({type(x)})')

    gen2 = my_range2(1, Decimal(2.0), Decimal(5.0))
    for x in gen2:
        print(f'gen2={x}({type(x)})')

以下の結果となります。

gen1=1(<class 'int'>)
gen1=3(<class 'decimal.Decimal'>)
gen2=1(<class 'decimal.Decimal'>)
gen2=3(<class 'decimal.Decimal'>)

サンプルコード

# coding: utf-8

from decimal import Decimal

def my_range1(begin, step, end=None):
    result = begin
    forever = end is None

    while forever or result < end:
        yield result
        result += step


def my_range2(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None

    while forever or result < end:
        yield result
        result += step


if __name__ == '__main__':
    gen1 = my_range1(1, 2, 5)
    for x in gen1:
        print(f'gen1={x}({type(x)})')
    
    gen2 = my_range2(1, 2, 5)
    for x in gen2:
        print(f'gen2={x}({type(x)})')

    print('---------------------------------------')

    gen1 = my_range1(1, 2.0, 5.0)
    for x in gen1:
        print(f'gen1={x}({type(x)})')

    gen2 = my_range2(1, 2.0, 5.0)
    for x in gen2:
        print(f'gen2={x}({type(x)})')

    print('---------------------------------------')

    gen1 = my_range1(1, Decimal(2.0), Decimal(5.0))
    for x in gen1:
        print(f'gen1={x}({type(x)})')

    gen2 = my_range2(1, Decimal(2.0), Decimal(5.0))
    for x in gen2:
        print(f'gen2={x}({type(x)})')

結果は

gen1=1(<class 'int'>)
gen1=3(<class 'int'>)
gen2=1(<class 'int'>)
gen2=3(<class 'int'>)
---------------------------------------
gen1=1(<class 'int'>)
gen1=3.0(<class 'float'>)
gen2=1.0(<class 'float'>)
gen2=3.0(<class 'float'>)
---------------------------------------
gen1=1(<class 'int'>)
gen1=3(<class 'decimal.Decimal'>)
gen2=1(<class 'decimal.Decimal'>)
gen2=3(<class 'decimal.Decimal'>)

となります。

python2 には、coerce って関数があったみたいですが廃止となったみたいですね。

以下も参考になりました。

stackoverflow.com

stackoverflow.com


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。