Excel показывает #VALUE!, когда мой UDF возвращает строку, содержащую более 255 символов.

Xlwings - 0.7.1, а excel - 2007, который, согласно Microsoft, может содержать до 32767 символов в ячейке.

В чем может быть проблема?

0
Jorge 25 Апр 2016 в 23:54

2 ответа

Лучший ответ

Насколько я могу судить, Py.CallUDF (используемый xlwings udfs) возвращает массив 2D Variant.

Также кажется, что по какой-то причине возврат массива Variant с длиной строки больше 255 из чистого UDF VBA приводит к ошибке #VALUE при вызове в excel. Размещение часов в массиве в редакторе VBA показывает, что данные не повреждены, просто они не передаются правильно. Небольшой поиск вернул несколько вопросов о максимальной длине строк в VBA, но ничего, что конкретно касалось этой проблемы.

Однако возврат массивов String или отдельных строк с> 255 символами работает нормально.

Вот несколько чистых примеров VBA, показывающих проблему:

Вернуть массив вариантов:

Function variant_long_string(n)
    Dim temp(0 To 0, 0 To 0) As Variant
    temp(0, 0) = String(n, "a")
    variant_long_string = temp
End Function

Вызов из Excel возвращает (не выполняется при N> 255):

255 aaaaaaaaaaaaa....aaaaaaaaa
256 #VALUE!

Вернуть элемент массива вариантов:

Function variant_long_string_element(n)
    Dim temp(0 To 0, 0 To 0) As Variant
    temp(0, 0) = String(n, "a")
    variant_long_string_element = temp(0, 0)
End Function

Вызов из Excel возвращает (успешно для N> 255):

255 aaaaaaaaaaaaa....aaaaaaaaa
256 aaaaaaaaaaaaa....aaaaaaaaaa

Возвращенный массив строк:

Function string_long_string(n)
    Dim temp(0 To 0, 0 To 0) As String
    temp(0, 0) = String(n, "a")
    string_long_string = temp
End Function

Вызов из Excel возвращает (успешно для N> 255):

255 aaaaaaaaaaaaa....aaaaaaaaa
256 aaaaaaaaaaaaa....aaaaaaaaaa

Временное решение

Если ваш Python UDF возвращает только одно строковое значение, например:

@xw.func    
def build_long_string(n):
    res = 'a'*int(n)
    return res 

Xlwings автоматически сгенерирует следующий макрос VBA в модуле xlwings_udfs:

Function build_long_string(n)
        If TypeOf Application.Caller Is Range Then On Error GoTo failed
        build_long_string = Py.CallUDF(PyScriptPath, "build_long_string", Array(n), ThisWorkbook)
        Exit Function
failed:
        build_long_string = Err.Description
End Function

В качестве быстрого патча, чтобы заставить ваш UDF работать, немного измените этот макрос на это:

Function build_long_string(n)
        If TypeOf Application.Caller Is Range Then On Error GoTo failed
        temp = Py.CallUDF(PyScriptPath, "build_long_string", Array(n), ThisWorkbook)
        build_long_string = temp(0, 0)
        Exit Function
failed:
        build_long_string = Err.Description
End Function

Позволяет строке> 255 для успешного преобразования в Excel. Вы можете сделать что-то подобное для результата массива, вам просто нужно будет преобразовать массив Variant в массив String путем цикла / переназначения всех значений из temp на результат.

2
schoolie 29 Апр 2016 в 18:46

Основываясь на предложении @schoolie выше о преобразовании массива 2D Variant в массив 2D String, я изменил источник логики генерации функции VBA в моем локальном xlwings:

В udfs.generate_vba_wrapper ()

Заменить:

vba.write('{fname} = Py.CallUDF("{module_name}", "{fname}", {args_vba}, ThisWorkbook)\n',
                    module_name=module_name,
                    fname=fname,
                    args_vba=args_vba,
                )

С участием:

vba.write('r = Py.CallUDF("{module_name}", "{fname}", {args_vba}, ThisWorkbook)\n',
                                        module_name=module_name,
                                        fname=fname,
                                        args_vba=args_vba,
                                    )                    
                vba.write('ReDim strarray(UBound(r, 1), UBound(r, 2)) As String\n')
                vba.write('For i = 0 To UBound(r, 1)\n')
                vba.write('  For j = 0 To UBound(r, 2)\n')
                vba.write('    strarray(i, j) = CStr(r(i, j))\n')
                vba.write('  Next\n')
                vba.write('Next\n')
                vba.write('{fname} = strarray\n', fname=fname)

Другой вариант - исправить сгенерированный макрос VB в редакторе VB после выполнения «Импортировать Python UDF». Однако это изменение будет потеряно при повторном импорте. Код уже предоставлен @schoolie выше

2
Jorge 6 Май 2016 в 16:44