# 转换器和选项
在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`,但这部分需要更多的工作,目前没有文档。