# 轉換器和選項
在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`:

~~~
>>> 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:**

~~~
>>> 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:**

~~~
>>> 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`,但這部分需要更多的工作,目前沒有文檔。