コンバーターおよびオプション

コンバーターはv0.7.0で導入された機能で、Excelのセル範囲やその値の 読み込み書き込み 時の変換方法を定義するものです。この機能は、xlwings.Range オブジェクトと User Defined Functions (UDF)の両方で同じように利用できます。

コンバーターを使うには、 Range オブジェクトの場合は options メソッドで、UDFの場合は @xw.arg@xw.ret デコレーターで、明示的に指定します。コンバーターの指定が無ければ、読み込み時にはデフォルト コンバーターが適用されます。書き込み時には、Excelに書きこまれるオブジェクトの型に応じて、xlwingsは(可能であれば)適切なコンバーターを自動的に適用します。型に合うコンバーターが無ければ、デフォルト コンバーターにフォール バックします。

以下、全てのサンプル コードは次のインポートを前提としています:

>>> import xlwings as xw

シンタックス:

xw.Range

UDFs

読み込み

xw.Range.options(convert=None, **kwargs).value

@arg('x', convert=None, **kwargs)

書き込み

xw.Range.options(convert=None, **kwargs).value = myvalue

@ret(convert=None, **kwargs)

注釈

キーワード引数(kwargs)は、特定のコンバーターやデフォルト コンバーターで使用されます。例えば、デフォルトコンバーターの numbers オプションやDataFrame コンバーターの index オプションの設定方法は以下のとおりです:

xw.Range('A1:C3').options(pd.DataFrame, index=False, numbers=int).value

デフォルト コンバーター

オプションが設定されていない場合の変換は次のとおりです:

  • 単独セルは、Excelセルの値が数字であれば float 、テキストであれば unicode 、日付であれば datetime 、空白であれば None として読み込まれます。

  • 行/列は、リストとして読み込まれます、e.g. [None, 1.0, 'a string']

  • 2次元のセル範囲は、リストのリストとして読み込まれます、e.g. [[None, 1.0, 'a string'], [None, 2.0, 'another string']]

以下のオプションを設定できます:

  • ndim

    セル範囲の形に関わらず、強制的に値の次元を1か2にします:

    >>> import xlwings as xw
    >>> sht = xw.Book().sheets[0]
    >>> sht.range('A1').value = [[1, 2], [3, 4]]
    >>> sht.range('A1').value
    1.0
    >>> sht.range('A1').options(ndim=1).value
    [1.0]
    >>> sht.range('A1').options(ndim=2).value
    [[1.0]]
    >>> sht.range('A1:A2').value
    [1.0 3.0]
    >>> sht.range('A1:A2').options(ndim=2).value
    [[1.0], [3.0]]
    
  • numbers

    デフォルトでは数字のセルは float として読み込まれますが、 int に変更できます:

    >>> sht.range('A1').value = 1
    >>> sht.range('A1').value
    1.0
    >>> sht.range('A1').options(numbers=int).value
    1
    

    それ以外にも、1つのfloat引数を受け取る関数や型を設定することもできます。

    UDFでの使用例を以下に示します:

    @xw.func
    @xw.arg('x', numbers=int)
    def myfunction(x):
        # all numbers in x arrive as int
        return x
    

    Note: 内部的にはExcelは常に数字をfloatとして保持しています。そのため、 int コンバーターは、最初に数字を丸めてから、int に変換しています。そうしなければ、例えば、5が5よりも僅かに小さい数字として表されている場合には、5は4として返されるためです。Pythonのオリジナルの int が必要であれば、代わりに raw int を使用します。

  • dates

    デフォルトでは、日付のセルは datetime.datetime として読み込まれますが、datetime.date に変更することもできます。

    • Range:

      >>> import datetime as dt
      >>> sht.range('A1').options(dates=dt.date).value
      
    • UDFs: @xw.arg('x', dates=dt.date)

    それ以外にも、 datetime.datetime と同じキーワード引数を受け取る任意の関数や型を指定できます。例:

    >>> my_date_handler = lambda year, month, day, **kwargs: "%04i-%02i-%02i" % (year, month, day)
    >>> sht.range('A1').options(dates=my_date_handler).value
    '2017-02-20'
    
  • empty

    空白のセルはデフォルトでは None に変換されますが、次のように変更できます:

    • Range: >>> sht.range('A1').options(empty='NA').value

    • UDFs: @xw.arg('x', empty='NA')

  • transpose

    transposeは読み込み/書き込み時に機能し、例えばリストを列方向に書き込めます:

    • Range: sht.range('A1').options(transpose=True).value = [1, 2, 3]

    • UDFs:

      @xw.arg('x', transpose=True)
      @xw.ret(transpose=True)
      def myfunction(x):
          # x will be returned unchanged as transposed both when reading and writing
          return x
      
  • expand

    これはRangeの table, vertical, horizontal プロパティと同じように機能しますが、Rangeの値を取得するときのみ評価されます:

    >>> import xlwings as xw
    >>> sht = xw.Book().sheets[0]
    >>> sht.range('A1').value = [[1,2], [3,4]]
    >>> rng1 = sht.range('A1').expand()
    >>> rng2 = sht.range('A1').options(expand='table')
    >>> rng1.value
    [[1.0, 2.0], [3.0, 4.0]]
    >>> rng2.value
    [[1.0, 2.0], [3.0, 4.0]]
    >>> sht.range('A3').value = [5, 6]
    >>> rng1.value
    [[1.0, 2.0], [3.0, 4.0]]
    >>> rng2.value
    [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]
    

    注釈

    expand メソッドが使えるのは、 Range オブジェクトのみです。UDFは呼び出し元のセルの操作のみしかできないため、UDFでは expand メソッドを使えません。

  • chunksize

    When you read and write from or to big ranges, you may have to chunk them or you will hit a timeout or a memory error. The ideal chunksize will depend on your system and size of the array, so you will have to try out a few different chunksizes to find one that works well:

    import pandas as pd
    import numpy as np
    sheet = xw.Book().sheets[0]
    data = np.arange(75_000 * 20).reshape(75_000, 20)
    df = pd.DataFrame(data=data)
    sheet['A1'].options(chunksize=10_000).value = df
    

    And the same for reading:

    # As DataFrame
    df = sheet['A1'].expand().options(pd.DataFrame, chunksize=10_000).value
    # As list of list
    df = sheet['A1'].expand().options(chunksize=10_000).value
    

ビルトイン コンバーター

xlwingsはビルトイン コンバーターを提供しており、これらは、辞書Numpy arrays, Pandas Series, DataFrame への型変換に対応しています。これらはデフォルト コンバーターを基にしているため、多くのケースで上記のオプションも利用できます(例えば辞書の場合の ndim のように意味がないこともありますが)。

これら以外の型に対応するカスタム コンバーターの作成や登録もできます(後述)。

以下のサンプルは xlwings.Range オブジェクトとUDFの両方で使えますが、いずれかで説明します。

辞書コンバーター

辞書コンバーターはExcelの2つの列を辞書に変換します。もしデータが行方向なら、 transpose を使ってください:

_images/dict_converter.png
>>> sht = xw.sheets.active
>>> sht.range('A1:B2').options(dict).value
{'a': 1.0, 'b': 2.0}
>>> sht.range('A4:B5').options(dict, transpose=True).value
{'a': 1.0, 'b': 2.0}

Note: dict の代わりに、 collectionsOrderedDict も使えます。

Numpy array コンバーター

options: dtype=None, copy=True, order=None, ndim=None

最初の3つのオプションは、直接 np.array() を使うときと同じように機能します。また、 ndim は先述のリスト(デフォルト コンバーター)と同様に機能し、numpyのスカラーや1次元配列、2次元配列を返します。

:

>>> import numpy as np
>>> sht = xw.Book().sheets[0]
>>> sht.range('A1').options(transpose=True).value = np.array([1, 2, 3])
>>> sht.range('A1:A3').options(np.array, ndim=2).value
array([[ 1.],
       [ 2.],
       [ 3.]])

Pandas Series コンバーター

options: dtype=None, copy=False, index=1, header=True

最初の2つのオプションは、直接 pd.Series を使うときと同じように機能します。Pandas Seriesは常に列方向への入出力を前提としているため、 ndim は機能しません。

index: int or Boolean
読み込み時、Excel上のデータからインデックスとする列の数を設定します。
書き込み時、 インデックスの有無を TrueFalse かで設定します。
header: Boolean
読み込み時、ExcelにインデックスまたはSeriesの名前がなければ、False を設定します。
書き込み時、インデックスやシリーズの名前の有無を、 TrueFalse かで設定します。

indexheader には、1True の両方を使えます。

_images/series_conv.png
>>> sht = xw.Book().sheets[0]
>>> s = sht.range('A1').options(pd.Series, expand='table').value
>>> s
date
2001-01-01    1
2001-01-02    2
2001-01-03    3
2001-01-04    4
2001-01-05    5
2001-01-06    6
Name: series name, dtype: float64
>>> sht.range('D1', header=False).value = s

Pandas DataFrame コンバーター

options: dtype=None, copy=False, index=1, header=1

最初の2つのオプションは、直接 pd.DataFrame() を使ったときと同じように機能します。Pandas DataFrameは自動的に ndim=2 として読み込むため、ndim は機能しません。

index: int or Boolean
読み込み時、Excel上のデータからインデックスとする列の数を設定します。
書き込み時、 インデックスの有無を TrueFalse かで設定します。
header: int or Boolean
読み込み時、Excel上のデータから列ヘッダーの数を設定します。
書き込み時、インデックスやシリーズの名前の有無を、 TrueFalse かで設定します。

indexheader には、1True の両方を使えます。

_images/df_converter.png
>>> sht = xw.Book().sheets[0]
>>> df = sht.range('A1:D5').options(pd.DataFrame, header=2).value
>>> df
    a     b
    c  d  e
ix
10  1  2  3
20  4  5  6
30  7  8  9

# Writing back using the defaults:
>>> sht.range('A1').value = df

# Writing back and changing some of the options, e.g. getting rid of the index:
>>> sht.range('B7').options(index=False).value = df

同じ例(スクリーンショットで Range('A13') から始まるもの)は UDF では以下のようになります:

@xw.func
@xw.arg('x', pd.DataFrame, header=2)
@xw.ret(index=False)
def myfunction(x):
   # x is a DataFrame, do something with it
   return x

xw.Range コンバーターおよび 'raw' コンバーター

技術的には、これらは "コンバーターではありません"。

  • xlwings.Range オブジェクトに直接アクセスする必要がある場合、次のようにします:

    @xw.func
    @xw.arg('x', 'range')
    def myfunction(x):
       return x.formula
    

    これはxを xlwings.Range オブジェクト、つまり、コンバーターやオプションを何も適用せずに返します。

  • raw コンバーターは、基礎となるライブラリー(Windowsでは pywin32 、Macでは appscript )に変換ぜずに値を渡します。つまり、不要なものの除去やクロスプラットフォーム用の調整がされていない値が作られます。効率化が必要な場合には、この方法が役に立つことがあるかもしれません。例:

    >>> sht.range('A1:B2').value
    [[1.0, 'text'], [datetime.datetime(2016, 2, 1, 0, 0), None]]
    
    >>> sht.range('A1:B2').options('raw').value  # or sht.range('A1:B2').raw_value
    ((1.0, 'text'), (pywintypes.datetime(2016, 2, 1, 0, 0, tzinfo=TimeZoneInfo('GMT Standard Time', True)), None))
    

カスタム コンバーター

自身で作成したコンバーターを実装する手順は以下のとおりです:

  • xlwings.conversion.Converter を継承します。

  • read_value メソッドと write_value メソッドをスタティックメソッドまたはクラスメソッドで実装します:

    • read_value では、 value はbase コンバーターの戻り値です: したがって、base が特定されなければ、デフォルト コンバーターのものになります。

    • write_value では、 value はExcelに書き込まれるオリジナルのオブジェクトです。戻り値は、base コンバーターが受け取れる形式でなければなりません。base が特定されていなければ、デフォルト コンバーターの形式になります。

    options 辞書は、 xw.Range.options メソッド(例えば、 xw.Range('A1').options(myoption='some value') )や、UDF使用時の @arg デコレーターや @ret デコレーターで指定した、全てのキーワード引数を含みます。 基本的な構造は次のとおりです:

    from xlwings.conversion import Converter
    
    class MyConverter(Converter):
    
        @staticmethod
        def read_value(value, options):
            myoption = options.get('myoption', default_value)
            return_value = value  # Implement your conversion here
            return return_value
    
        @staticmethod
        def write_value(value, options):
            myoption = options.get('myoption', default_value)
            return_value = value  # Implement your conversion here
            return return_value
    
  • Optional: 既存のコンバーターを基にコンバーターを作るには、base コンバーター(base はクラスの名前)をセットします。既存のコンバーターの例はビルトインコンバーターです: DictCoverterNumpyArrayConverterPandasDataFrameConverterPandasSeriesConverter

  • Optional: コンバーターを登録します: (a) 型を登録すれば、その型を書き込む際ののデフォルト コンバーターになります。そして/または (b) エイリアスを登録すれば、正確なクラス名ではなく、名前でコンバーターを指定できるようになります。

以下の例は理解の助けになるでしょう。ビルトイン DataFrame コンバーターを基に、nanを除去する機能を追加したDataFrame コンバーターを定義しています:

from xlwings.conversion import Converter, PandasDataFrameConverter

class DataFrameDropna(Converter):

    base = PandasDataFrameConverter

    @staticmethod
    def read_value(builtin_df, options):
        dropna = options.get('dropna', False)  # set default to False
        if dropna:
            converted_df = builtin_df.dropna()
        else:
            converted_df = builtin_df
        # This will arrive in Python when using the DataFrameDropna converter for reading
        return converted_df

    @staticmethod
    def write_value(df, options):
        dropna = options.get('dropna', False)
        if dropna:
            converted_df = df.dropna()
        else:
            converted_df = df
        # This will be passed to the built-in PandasDataFrameConverter when writing
        return converted_df

これらコンバーターの動作の違いを見てみましょう:

# Fire up a Workbook and create a sample DataFrame
sht = xw.Book().sheets[0]
df = pd.DataFrame([[1.,10.],[2.,np.nan], [3., 30.]])
  • デフォルトのDataFrame コンバーター:

    # Write
    sht.range('A1').value = df
    
    # Read
    sht.range('A1:C4').options(pd.DataFrame).value
    
  • DataFrameDropna コンバーター:

    # Write
    sht.range('A7').options(DataFrameDropna, dropna=True).value = df
    
    # Read
    sht.range('A1:C4').options(DataFrameDropna, dropna=True).value
    
  • エイリアスの登録(optional):

    DataFrameDropna.register('df_dropna')
    
    # Write
    sht.range('A12').options('df_dropna', dropna=True).value = df
    
    # Read
    sht.range('A1:C4').options('df_dropna', dropna=True).value
    
  • DataFrameDropnaを、DataFrameに対するデフォルトのコンバーターとして登録(optional):

    DataFrameDropna.register(pd.DataFrame)
    
    # Write
    sht.range('A13').options(dropna=True).value = df
    
    # Read
    sht.range('A1:C4').options(pd.DataFrame, dropna=True).value
    

これらのサンプルはUDFでも同様に機能します。例:

@xw.func
@arg('x', DataFrameDropna, dropna=True)
@ret(DataFrameDropna, dropna=True)
def myfunction(x):
    # ...
    return x

注釈

Pythonオブジェクトは、複数の変換パイプラインのステージを経て、Excelに書き出されます。反対方向、つまり、Excel/COM オブジェクトがPythonに読み込まれる際も同様です。

内部的には、パイプラインは複数の Accessor クラスで定義されています。コンバーターは特別なAccessorで、デフォルトAccessorのパイプラインに特別なステージを追加することで、特定の型への/からの変換を行います。例えば、 PandasDataFrameConverter は、(デフォルトのAccessorが渡す)リストのリストからPandas DataFrameに変換する方法を定めています。

Converter クラスは、新しいコンバーターを簡単に作るための基本的な枠組みを提供します。もっと細かい操作が必要なら、直接 Accessor クラスを継承することもできますが、作業が大変になることに加え、現在のところドキュメント化もされていません。