Есть ли способ вызвать код Python в Excel-VBA?

У меня есть файл Excel (Main.xlsm), содержащий макросы. У меня есть файл Python (python.py) для создания вспомогательного файла Excel (sub.xlsx), который я бы далее назвал в макросах файла Main.xlsm. Этот sub.xlsx файл, который генерируется при запуске python.py, сохраняется в том же рабочем каталоге.

Теперь я хочу, чтобы этот python.py выполнялся во время выполнения макросов Main.xlsm, а затем использовал этот xlsx файл. Я в основном хочу уменьшить шаг выполнения Python.py извне. Есть ли команда для этого? Я новичок в VBA.

Ответ 1

Самый простой способ - запустить интерпретатор python с помощью команды Shell

Shell ("python.exe " & yourScript & " " & arguments)

Ответ 2

Да, есть. Мой предпочтительный способ сделать это через xlwings (https://www.xlwings.org/), но есть и другие варианты. XlWings отличная, потому что она бесплатна, с открытым исходным кодом и проста в использовании, с отличной документацией. Однако есть некоторые ограничения по функциям, поэтому вам нужно будет проверить, соответствует ли оно вашим потребностям.

Ответ 3

Существует несколько способов запустить скрипт Python с VBA в зависимости от того, нужно ли вам ждать окончания выполнения и знать, прошло ли оно без ошибок.

С Shell, асинхронный с консолью:

Public Sub RunPython(file As String, ParamArray args())
  Shell "python.exe """ & file & """ " & Join(args, " ")
End Sub

С Shell, синхронно без консоли:

Public Function RunPython(file As String, ParamArray args())
  Shell "pythonw.exe """ & file & """ " & Join(args, " ")
End Function

С WScript.Shell, синхронно без консоли и с кодом выхода:

Public Function RunPython(file As String, ParamArray args()) As Long
  Dim obj As Object
  Set obj = CreateObject("WScript.Shell")
  RunPython = obj.Run("pythonw.exe """ & file & """ " & Join(args, " "), 0, True)
End Function

Ответ 4

У меня был целый месяц Python в моем блоге прямо здесь. Я устанавливаю шаблон, который я называю классом шлюза, который является классом Python с поддержкой COM, он будет регистрироваться сам при запуске из командной строки и после регистрации создается с помощью CreateObject ("foo.bar").

Вот хороший пример VBA, вызывающей класс Python, который использует некоторые функции scipy

import numpy as np
import pandas as pd
from scipy.stats import skewnorm


class PythonSkewedNormal(object):
    _reg_clsid_ = "{1583241D-27EA-4A01-ACFB-4905810F6B98}"
    _reg_progid_ = 'SciPyInVBA.PythonSkewedNormal'
    _public_methods_ = ['GeneratePopulation', 'BinnedSkewedNormal']

    def GeneratePopulation(self, a, sz):
        # https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.random.seed.html
        np.random.seed(10)
        # https://docs.scipy.org/doc/scipy-0.19.1/reference/generated/scipy.stats.skewnorm.html
        return skewnorm.rvs(a, size=sz).tolist()

    def BinnedSkewedNormal(self, a, sz, bins):
        # https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.random.seed.html
        np.random.seed(10)
        # https://docs.scipy.org/doc/scipy-0.19.1/reference/generated/scipy.stats.skewnorm.html
        pop = skewnorm.rvs(a, size=sz)
        bins2 = np.array(bins)
        bins3 = pd.cut(pop, bins2)

        table = pd.value_counts(bins3, sort=False)

        table.index = table.index.astype(str)

        return table.reset_index().values.tolist()

if __name__ == '__main__':
    print("Registering COM server...")
    import win32com.server.register
    win32com.server.register.UseCommandLine(PythonSkewedNormal)

и вызывающий код VBA

Option Explicit

Sub TestPythonSkewedNormal()

    Dim skewedNormal As Object
    Set skewedNormal = CreateObject("SciPyInVBA.PythonSkewedNormal")

    Dim lSize As Long
    lSize = 100

    Dim shtData As Excel.Worksheet
    Set shtData = ThisWorkbook.Worksheets.Item("Sheet3") '<--- change sheet to your circumstances
    shtData.Cells.Clear

    Dim vBins
    vBins = Array(-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5)

    'Stop
    Dim vBinnedData
    vBinnedData = skewedNormal.BinnedSkewedNormal(-5, lSize, vBins)

    Dim rngData As Excel.Range
    Set rngData = shtData.Cells(2, 1).Resize(UBound(vBins) - LBound(vBins), 2)

    rngData.Value2 = vBinnedData

    'Stop

End Sub

Полный комментарий можно найти в оригинальной записи в блоге здесь.

Преимущество здесь в том, что обстрела нет. Когда код возвращается, вы знаете, что он закончился, при обстреле один раз нужно проверить, завершился ли процесс шелла и т.д. Этот класс шлюза намного лучше ИМХО.