19

I have an Excel file (Main.xlsm) containing macros. I have a Python file (python.py) to generate a subsidiary Excel file (sub.xlsx) which I would further call in the macros of Main.xlsm-file. This sub.xlsx-file which is generated by the run of python.py is saved in the same working directory.

Now I want to make this python.py to be executed during the run of the Main.xlsm macros and then use this xlsx-file. I basically want to reduce the step of executing python.py externally. Is there a command for that? I am new to VBA.

Ratmir Asanov
  • 6,237
  • 5
  • 26
  • 40
Pratheek P Manangi
  • 341
  • 1
  • 3
  • 11
  • 1
    then stay new to (and away from) VBA :) I hope you find what you need. Interesting question. – Jean-François Fabre Jul 31 '17 at 08:13
  • 1
    @Jean-FrançoisFabre Thanks for your interest shown in the question. :p – Pratheek P Manangi Jul 31 '17 at 11:15
  • from my point of view (also applicable to other proprietary and cumbersome systems), sometimes it's best to 1) export data as CSV 2) run a python script to process the data easily and 3) reimport to excel/whatever so we also use this trick instead of trying to code in sub-languages :) – Jean-François Fabre Jul 31 '17 at 12:01
  • I use a "gateway class". So called gateway because it opens the rich world of the Python ecosystem to the Excel VBA Developer http://exceldevelopmentplatform.blogspot.com/2018/06/python-vba-curve-building.html – S Meaden Jun 11 '18 at 20:32
  • I have added a full answer (with a different example). – S Meaden Dec 31 '18 at 00:33
  • Possible duplicate of [Calling python script from excel/vba](https://stackoverflow.com/q/3567365/608639), [How to call python script on excel vba?](https://stackoverflow.com/q/18135551/608639), [Is there a way to call a Python code in Excel-VBA?](https://stackoverflow.com/q/45410316/608639), [Run and execute a python script from VBA](https://stackoverflow.com/q/30531662/608639), etc. – jww Nov 15 '19 at 12:34

4 Answers4

29

The simplest way is to run the python interpreter with the Shell command

Shell ("python.exe " & yourScript & " " & arguments)
Uri Goren
  • 13,386
  • 6
  • 58
  • 110
23

Yes, there is. My preferred way of doing this is through xlwings (https://www.xlwings.org/), but there are several other options as well. XlWings is great because it's free, open source and easy to use, with great documentation. There are some feature limitations though, so you'd have to check if it fits your needs.

Gabor
  • 685
  • 2
  • 11
  • 25
20

There are multiple ways tu run a python script with VBA depending on whether you need to wait for the end of the execution and know if it went without error.

With Shell, asynchronous with console:

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

With Shell, synchronous without console:

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

With WScript.Shell, synchronous without console and with exit code:

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
Florent B.
  • 41,537
  • 7
  • 86
  • 101
3

I had a whole Python month on my blog right here. I establish a pattern which I call the gateway class which is a COM enabled Python class, it will register itself if run from the command line and once registered is instantiated with CreateObject("foo.bar").

Here is a good example of VBA calling a Python class that uses some scipy functions

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)

and the calling VBA code

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

Full commentary can be found at the original blog entry here

The advantage here is that there is no shelling. When the code it returns, you know it has finished, with shelling once has to check if the shelled process has ended etc. This gateway class is much better IMHO.

S Meaden
  • 8,050
  • 3
  • 34
  • 65
  • Interesting proposal! However, correct me if I'm wrong, but now you've got two processes to start (python script, then Excel), correct? – Joël Jan 04 '19 at 09:29
  • @Joel: If if were two processes than that would be the same as shelling. However, if the bitness is the same then the python runtime dll is loaded into Excel.exe. If bitness differs then yes there are two processes, here is my investigation on that subject https://exceldevelopmentplatform.blogspot.com/2018/06/vba-python-python-com-class-has-own.html – S Meaden Jan 04 '19 at 11:42
  • ModuleNotFoundError: No module named 'win32com' – drgs Aug 24 '19 at 21:58
  • @drgs No module named 'xyz' means you need to install 'xyz'. – S Meaden Aug 25 '19 at 16:37
  • @SMeaden After running your code, I am getting this error: `TypeError: Can't locate the script hosting the COM object - please set _reg_class_spec_ in your object`. I also tried registering the same in cmd using `test1.py --register`. I then get a module not found error for NumPy. Although I already have NumPy installed. Can you please help me? – Krishna Kumar Apr 30 '21 at 14:46
  • @Krisha Kumar : try this http://dzmns.co/how-to-build-a-com-visible-executable-with-python/ _reg_class_spec_ = __name__ + ". – S Meaden Apr 30 '21 at 21:47
  • Thanks, @SMeaden I am now able to register. However, is there a way that I can directly have access to both the methods `['GeneratePopulation', 'BinnedSkewedNormal']` in excel via `Excel RTD function`. Since I am not much into VBA. – Krishna Kumar May 03 '21 at 11:58
  • Not currently no. – S Meaden May 03 '21 at 14:14