# 转换器和选项 在v0.7.0中引入,转换器定义了在**读取**和**写入**操作期间Excel Range及其值的转换方式。 它们还提供了跨**xlwings.Range**对象和**用户自定义函数**(UDF)的一致体验。 在使用UDF时,在操作`Range`对象时或在`@ xw.arg`和`@ xw.ret`装饰器中,转换器在`options`方法中明确设置。 如果未指定转换器,则在读取时应用默认转换器。 写入时,xlwings将根据写入Excel的对象类型自动应用正确的转换器(如果可用)。 如果没有找到该类型的转换器,它将回退到默认转换器。 以下所有代码示例取决于下面导入: ~~~ >>> import xlwings as xw ~~~ **Syntax:** |   | **xw.Range** | **UDFs** | | --- | --- | --- | | **reading** | `xw.Range.options(convert=None, **kwargs).value` | `@arg('x', convert=None, **kwargs)` | | **writing** | `xw.Range.options(convert=None, **kwargs).value = myvalue` | `@ret(convert=None, **kwargs)` | >[info]注意 关键字参数(`kwargs`)可以指特定转换器或默认转换器。 例如,要设置默认转换器中的`numbers`选项和DataFrame转换器中的`index`选项,您可以编写: ~~~ xw.Range('A1:C3').options(pd.DataFrame, index=False, numbers=int).value ~~~ ## 默认转换器 如果未设置任何选项,则执行以下转换: * 如果Excel单元格包含数字,则单个单元格被读取为“floats”;如果Excel单元格包含文本,则读取为“unicode”;如果Excel单元格包含日期,则读取为“datetime”;如果Excel单元格为空,则读取为“none”。 * 列/行作为列表读入,例如`[None, 1.0, 'a string']` * 将2维单元格Range作为列表列表读入,例如 `[[None, 1.0, 'a string'], [None, 2.0, 'another string']]` 可以设置以下选项: * **ndim** 强制该值具有1或2个维度,而不考虑Range的形状: ~~~ >>> 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 ~~~ 或者,可以指定采用单个float参数的任何其他函数或类型。 在UDF上使用它的方式如下: ~~~ @xw.func @xw.arg('x', numbers=int) def myfunction(x): # all numbers in x arrive as int return x ~~~ **注意:** Excel内部总是将数字存储为浮点数,这就是为什么int转换器在将数字转换为整数之前首先对数字进行舍入的原因。否则,例如5可能返回为4,以防它表示为略小于5的浮点数。如果在转换器中需要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** 这适用于读取和写入,并允许我们(例如)在Excel的列方向中编写列表: * 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将在转置时保持不变 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]] ~~~ >[info]注意 `expand`方法仅适用于`Range`对象,因为UDF只允许操作调用单元格。 ## 内置转换器 XLwings提供了几个内置的转换器,可以执行到**字典**、**numpy数组**、**pandas系列**和**数据帧**的类型转换。这些都建立在默认转换器之上,因此在大多数情况下,上面描述的选项也可以在这个上下文中使用(除非它们毫无意义,例如字典中的`ndim`)。 也可以为其他类型编写和注册自定义转换器,请参见下文。 下面的示例可以与`xlwings.Range`对象和UDF一起使用,即使可能只显示一个版本。 ### 字典转换器 字典转换器将两个Excel列转换为字典。 如果数据是行方向,请使用`transpose`: ![](https://i.vgy.me/rcfYPS.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} ~~~ 注意:您也可以使用`collections`中的`OrderedDict`代替`dict`。 ### Numpy阵列转换器 **options:** `dtype=None, copy=True, order=None, ndim=None` 前3个选项的行为与直接使用`np.array()`时相同。另外,`ndim`的工作原理与上面的列表(在默认转换器下)相同,因此返回numpy scalars、1维数组或2维数组。 **Example**: ~~~ >>> 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()`时相同。` ndim`不会对Pandas Series产生影响,因为它们总是以列方向返回。 `index`: int 或 Boolean 读取时,它需要Excel中显示的索引列数。 写入时,通过将索引设置为`True`或`False`来包含或排除索引。 `header`: Boolean 读取时,如果Excel既不显示索引名也不显示序列名,请将其设置为`False`。 写入时,通过将索引和序列名设置为`True`或`False`,包括或排除索引和序列名。 对于`index`和`header`,`1`和`true`可以互换使用。 **Example:** ![](https://i.vgy.me/28APok.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` 前两个选项的行为与直接使用`pd.dataframe()`时相同。`ndim`不会对Pandas DataFrame产生影响,因为它们会自动以`ndim=2`读取。 `index`: int 或 Boolean 读取时,它需要Excel中显示的索引列数。 写入时,通过将索引设置为`True`或`False`来包含或排除索引。 `header`: int 或 Boolean 读取时,它需要Excel中显示的列标题数。 写入时,通过将索引和序列名设置为`True`或`False`,包括或排除索引和序列名。 对于`index`和`header`,`1`和`true`可以互换使用。 **Example:** ![](https://i.vgy.me/AB32NY.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 # 使用默认值回写: >>> sht.range('A1').value = df # 写回并更改一些选项,例如删除索引: >>> sht.range('B7').options(index=False).value = df ~~~ 对于**UDF**(从屏幕截图上的`range('a13')`开始)的相同示例如下: ~~~ @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’ 转换器 从技术上讲,这些都是 “no-converters”(无转换器)。 * 如果需要直接访问`xlwings.Range`对象,可以执行以下操作: ~~~ @xw.func @xw.arg('x', xw.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`,它将以默认转换器的格式到达。 * 在`write_value`中,`value`是写入Excel的原始对象。 它必须以基本转换器期望的格式返回。 同样,如果没有指定`base`,这是默认转换器。 `options`字典将包含`xw.Range.options`方法中指定的所有关键字参数,例如 当使用UDF时调用`xw.Range('A1')。options(myoption ='some value')`或者在`@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 ~~~ * 可选:设置一个`base`转换器(`base`需要一个类名)来构建在现有转换器之上,例如: 对于内置的:`DictCoverter`,`NumpyArrayConverter`,`PandasDataFrameConverter`,`PandasSeriesConverter` * 可选:注册转换器:您可以 **(a)** 注册一个类型,以便在写入操作期间转换器成为此类型的默认值和/或 **(b)** 您可以注册一个允许您使用的别名 显式调用转换器的名称而不是类名 以下示例应该更容易理解 - 它定义了一个DataFrame转换器,它扩展了内置的DataFrame转换器以添加对删除nan的支持: ~~~ 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 # 这将在使用dataframedropna转换器进行读取时到达python。 return converted_df @staticmethod def write_value(df, options): dropna = options.get('dropna', False) if dropna: converted_df = df.dropna() else: converted_df = df # 这将在写入时传递给内置的PandasDataFrameConverter return converted_df ~~~ 现在让我们看看如何应用不同的转换器: ~~~ # 启动工作簿并创建示例DataFrame sht = xw.Book().sheets[0] df = pd.DataFrame([[1.,10.],[2.,np.nan], [3., 30.]]) ~~~ * DataFrames的默认转换器: ~~~ # 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 ~~~ * 注册别名(可选): ~~~ 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注册为DataFrames的默认转换器(可选): ~~~ 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 ~~~ >[info]注意 当Python对象被写入Excel时,它们会在转换管道的多个阶段中运行。当excel/com对象被读取到python中时,另一个方向也是如此。 管道由`Accessor`类在内部定义。 转换器只是一个特殊的Accessor,它通过向默认Accessor的管道添加一个额外的阶段来转换为特定类型。 例如,`PandasDataFrameConverter`定义了如何将列表列表(由默认Accessor提供)转换为Pandas DataFrame。 `Converter`类提供了基本的脚手架,使编写新转换器的任务更容易。 如果你需要更多的控制,你可以直接子类化`Accessor`,但这部分需要更多的工作,目前没有文档。