Excel vba from python

При обработке данных Excel файлов используя Python, бывают ситуации, когда необходимо запустить с помощью Python макрос VBA, написанный ранее в Excel. В этой статье мы разберемся как это сделать с применением библиотеки Xlwings.

Как запустить VBA макрос при помощи Python

Для нашего учебного примера давайте создадим Excel файл и в нем макрос:

Sub SampleMacro()
MsgBox («VBA макрос запущен при помощи Python»)
End Sub

Как запустить VBA макрос при помощи Python

Сохраните Excel файл c поддержкой макросов под именем «vba_python.xlsm». Кстати, если вы позабыли, как создавать VBA макросы, то прочитайте нашу статью «

Как удалить скрытые имена в Excel

», в ней подробно мы разбирали этот вопрос.

Теперь запускаем Python и импортируем библиотеку Xlwings:

import xlwings as xw

Следующим этапом открываем Excel файл, который создали ранее:

vba_book = xw.Book(«vba_python.xlsm»)

Воспользуемся встроенным в Xlwings методом macro(), который как раз и предназначен для запуска VBA макросов. В качестве параметра в метод передается названия созданного ранее VBA макроса.:

vba_macro = vba_book.macro(«SampleMacro»)

Теперь запускаем макрос:

vba_macro()

Итого общий код нашего минипроекта выглядит следующим образом:

Как запустить VBA макрос при помощи Python

Выполнив этот код, мы получим следующий результат в Excel файле:

Как запустить VBA макрос при помощи Python

Отлично, все работает!

Хотел бы обратить ваше внимание, что ваше знание Python и Excel очень поможет в освоении новой, перспективной и востребованной профессии «Data Scientist». Если хотите узнать об этом побольше, запишитесь на курс «

Data Scientist с нуля до Junior

» от Skillbox.

Спасибо за внимание, жду ваших вопросов в комментариях.

Last Updated on March 26, 2022 by

Sometimes we might want to run Excel VBA Macro from Python. This is useful when we want to run existing Excel macros – we can execute those VBA macros without even opening the Excel file.

Library

We’ll use the py32win library for this demonstration. Note the py32win library doesn’t have a “normal” naming convention. To install it, type the following in a command prompt:

pip install pywin32

However, to import the library in Python, the name becomes win32com.client:

import win32com.client

Here’s a very simple Excel Vba macro – it takes an argument, writes a greeting message in cell B2 of the “Sheet1” tab. You can create any macro and run it with Python.

Sub macro1(name As String):
    Worksheets("Sheet1").Range("B2").Value = "hello " & name & "!"
End Sub

Note in the below screenshot, this macro has the name macro1, and it’s inside Module1 of the workbook.

Excel VBA Macro Editor
Excel VBA Macro Editor

Run VBA Macro From Python

Now we’ll write the Python code, starting with importing win32com.client and instantiating an “xl” object to represent the Microsoft Excel application.

Then we open the Excel file that contains the VBA macro and assign it to a variable wb.

import win32com.client
xl = win32com.client.Dispatch("Excel.Application")  #instantiate excel app

wb = xl.Workbooks.Open(r'C:UsersjayDesktopPythonInOfficepython_run_macromacro.xlsm')
xl.Application.Run('macro.xlsm!Module1.macro1("Jay")')
wb.Save()
xl.Application.Quit()

To call the VBA Macro, we use the xl.Application.Run() method. The argument indicates which macro to run: xl.Application.run(‘macro.xlsm!Module1.macro1(“Jay”)’). Let’s break this down:

  1. macro.xlsm!: the file that contains the VBA macro, no need to include the full path here
  2. Module1: the module that contains the macro
  3. macro1(“Jay”): the name of the macro, note we can pass arguments into VBA.

If your VBA macro doesn’t take any arguments, then just include the macro name without the brackets. For example: xl.Application.run(‘macro.xlsm!Module1.macro1’)

We then save the Excel file using wb.Save(). You can choose to use the VBA code to save the workbook too.

Last but not least, don’t forget to close the Excel app (in Python) by calling the xl.Application.Quit(). Leaving the Excel Application open in the background might cause problems with your program later on.

Additional Resources

Automate Word Document (.docx) With Python-docx And pywin32

Mail Merge Send Mass Email In Python

Get Outlook calendar meeting data using Python

Ezoic

Let’s say you have some Excel macros that you’d like to integrate with python. If you’ve got the time & know-how, and if it’s something that can be run independently, I’d recommend porting the VBA code to python, but if the functions and methods in VBA are actively used by non-developers, you can’t easily do this without scaling up a brand new application (with all that entails), and re-training the users. And you don’t want to re-write the logic in python, because now you’ve got two code bases to maintain.

So let’s assume that’s the use-case: you’ve got some VBA code that is actively used by clients/customers/non-technical co-workers, etc., and you want to leverage this already written & battle-tested code as part of a python application, perhaps to automate a manual process like “Open workbook, and run a specified macro which will produce a well-formatted report dashboard”, or something like that. Fortunately, it’s pretty easy to integrate.

Modeling the Excel Macro/Function in Python

With a pretty simple python class, you can model an Excel macro or function using this XlMacro class. (I was inspired to make this XlMacro class by a question that I answered on StackOverflow yesterday.)

The XlMacro only opens the Workbook (and Excel Application) when needed, via the Run method. By default, the Run method keeps the workbook/Excel open, this is convenient if you need to make multiple calls to the same method, but this can be overridden with the keep_open and save_changes keyword arguments.

import os
from win32com import client

EXCEL_CLS_NAME = "Excel.Application"

class XlMacro:
    def __init__(self, path, book, module, name, *args):
        self._path = path  # path containing workbook
        self._book = book  # workbook name like Book1.xlsm
        self._module = module  # module name, e.g., Module1
        self._name = name  # procedure or function name
        self._params = args  # argument(s)
        self._wb = None
    @property
    def workbook(self):
        return self._wb
    @property
    def wb_path(self):
        return os.path.join(self._path, self._book)
    @property
    def name(self):
        return f'{self._book}!{self._module}.{self._name}'
    @property
    def params(self):
        return self._params
    def get_workbook(self):
        wb_name = os.path.basename(self.wb_path)
        try:
            xl = client.GetActiveObject(EXCEL_CLS_NAME)
        except:
            # Excel is not running, so we need to handle it.
            xl = client.Dispatch(EXCEL_CLS_NAME)
        if wb_name in [wb.Name for wb in xl.Workbooks]:
            return xl.Workbooks[wb_name]
        else:
            return xl.Workbooks.Open(self.wb_path)
    def Run(self, *args, **kwargs):
        """ 
        Runs an Excel Macro or evaluates a UDF 
        returns None for macro, or the return value of Function
        NB: there is no error-handling here, but there should be!
        """
        keep_open = kwargs.get('keep_open', True)
        save_changes = kwargs.get('save_changes', False)
        self._wb = self.get_workbook()
        xl_app = self._wb.Application
        xl_app.Visible = True
        ret = None
        if args is None:
            ret = xl_app.Run(self.name)
        elif not args:
            # run with some default parameters
            ret = xl_app.Run(self.name, *self.params)
        else:
            ret = xl_app.Run(self.name, *args)
        if not keep_open:
            self.workbook.Close(save_changes)
            self._wb = None
            xl_app.Quit()
        return ret

Here’s some example code to loop over our available macros/functions with some dummy arguments.

# Modify these path/etc as needed.
path = r'c:debug\'
book = 'pymacro.xlsm'
module = 'Module1'
macros = ['macro1', 'SayThis', 'AddThings', 'GetFromExcel', 'GetWithArguments']

def default_params(macro):
    """
    mocks some dummy arguments for each Excel macro
    this is required by Excel.Application.Run(<method>,<ParamArray>)
    """
    d = {'macro1': ("hello", "world", 123, 18.45),
        'SayThis': ('hello, world!',),
        'AddThings': [13, 39],
        'GetFromExcel': [],
        'GetWithArguments': [2]
        }
    return d.get(macro)

# Test the macros and their arguments:
for m in macros:
    args = default_params(m)
    if args:
        macro = XlMacro(path, book, module, m, *args)
    else:
        macro = XlMacro(path, book, module, m)
    x = macro.Run()
    print(f'returned {x} from {m}({args})' if x else f'Successfully executed {m}({args})')

If you don’t want to use the mock arguments, you can initialize an XlMacro without *args, and then specify args while calling Run() method directly.

f = XlMacro(path, book, module, 'GetWithArguments' )
f.Run(3)

Example VBA Code

Here’s the example VBA code that you can use with the above. To use, simply drop this code in Module1 of any macro-enabled Excel Workbook, and Save the workbook.

Note: the practical case for this is not limited to the very simple examples included below, but these examples illustrate the basic foundation: if you can send arguments over COM from python to Excel (and vice versa) you can apply the same principle towards more complex use cases. In the real world, you’d send arguments to kick off an Excel procedure that creates a dashboard or report, formats or normalizes data, etc.

Option Explicit

Sub macro1(ParamArray arr())
    Dim msg As String
    msg = "Parameters received with length: " & (UBound(arr) + 1) & vbCrLf & vbTab
    msg = msg + Join(arr, vbNewLine + vbTab)
    MsgBox msg
End Sub

Sub SayThis(words As String)
    Application.Speech.Speak words
End Sub

Sub AddThings(i As Double, j As Double)
    Sheet1.Range("A1").Value = i + j
End Sub

Function GetFromExcel() As Double
    GetFromExcel = CDbl(Sheet1.Range("A1").Value)
End Function

Function GetWithArguments(i As Double) As Double
    GetWithArguments = Sheet1.Range("A1").Value * i 
End Function

What’s Next?

Play around with this code, and use it against some of your own VBA procedures, of course!

A more interesting case will be the reverse: calling python functions from VBA. It’s a little bit more complicated to get up and running, but allows you to retain a UI in familiar Microsoft applications, while taking advantage of python to do the heavy-lifting. I’ll cover that in a future post.

Время на прочтение
6 мин

Количество просмотров 349K

Добрый день, уважаемые читатели.

В сегодняшней статье я хотел бы, как можно подробнее, рассмотреть интеграцию приложений Python и MS Excel. Данные вопрос может возникнуть, например, при создании какой-либо системы онлайн отчетности, которая должна выгружать результаты в общепринятый формат ну или какие-либо другие задачи. Также в статье я покажу и обратную интеграцию, т.е. как использовать функцию написанную на python в Excel, что также может быть полезно для автоматизации отчетов.

Работаем с файлами MS Excel на Python

Для работы с Excel файлами из Python мне известны 2 варианта:

  1. Использование библиотек, таких как xlrd, xlwt, xlutils или openpyxl
  2. Работа с com-объектом

Рассмотрим работу с этими способами подробнее. В качестве примера будем использовать готовый файл excel из которого мы сначала считаем данные из первой ячейки, а затем запишем их во вторую. Таких простых примеров будет достаточно для первого ознакомления.

Использование библиотек

Итак, первый метод довольно простой и хорошо описан. Например, есть отличная статья для описания работы c xlrd, xlwt, xlutils. Поэтому в данном материале я приведу небольшой кусок кода с их использованием.

Для начала загрузим нужные библиотеки и откроем файл xls на чтение и выберем
нужный лист с данными:

import xlrd, xlwt
#открываем файл
rb = xlrd.open_workbook('../ArticleScripts/ExcelPython/xl.xls',formatting_info=True)

#выбираем активный лист
sheet = rb.sheet_by_index(0)

Теперь давайте посмотрим, как считать значения из нужных ячеек:

#получаем значение первой ячейки A1
val = sheet.row_values(0)[0]

#получаем список значений из всех записей
vals = [sheet.row_values(rownum) for rownum in range(sheet.nrows)]

Как видно чтение данных не составляет труда. Теперь запишем их в другой файл. Для этого создам новый excel файл с новой рабочей книгой:

wb = xlwt.Workbook()
ws = wb.add_sheet('Test')

Запишем в новый файл полученные ранее данные и сохраним изменения:

#в A1 записываем значение из ячейки A1 прошлого файла
ws.write(0, 0, val[0])

#в столбец B запишем нашу последовательность из столбца A исходного файла
i = 0
for rec in vals:
    ws.write(i,1,rec[0])
    i =+ i

#сохраняем рабочую книгу
wb.save('../ArticleScripts/ExcelPython/xl_rec.xls')

Из примера выше видно, что библиотека xlrd отвечает за чтение данных, а xlwt — за запись, поэтому нет возможности внести изменения в уже созданную книгу без ее копирования в новую. Кроме этого указанные библиотеки работают только с файлами формата xls (Excel 2003) и у них нет поддержки нового формата xlsx (Excel 2007 и выше).

Чтобы успешно работать с форматом xlsx, понадобится библиотека openpyxl. Для демонстрации ее работы проделаем действия, которые были показаны для предыдущих библиотек.

Для начала загрузим библиотеку и выберем нужную книгу и рабочий лист:

import openpyxl
wb = openpyxl.load_workbook(filename = '../ArticleScripts/ExcelPython/openpyxl.xlsx')
sheet = wb['test']

Как видно из вышеприведенного листинга сделать это не сложно. Теперь посмотрим как можно считать данные:

#считываем значение определенной ячейки
val = sheet['A1'].value

#считываем заданный диапазон
vals = [v[0].value for v in sheet.range('A1:A2')]

Отличие от прошлых библиотек в том, что openpyxl дает возможность отображаться к ячейкам и последовательностям через их имена, что довольно удобно и понятно при чтении программы.

Теперь посмотрим как нам произвести запись и сохранить данные:

#записываем значение в определенную ячейку
sheet['B1'] = val

#записываем последовательность
i = 0
for rec in vals:
    sheet.cell(row=i, column=2).value = rec
    i =+ 1

# сохраняем данные
wb.save('../ArticleScripts/ExcelPython/openpyxl.xlsx')

Из примера видно, что запись, тоже производится довольно легко. Кроме того, в коде выше, можно заметить, что openpyxl кроме имен ячеек может работать и с их индексами.

К недостаткам данной библиотеки можно отнести, то что, как и в предыдущем примере, нет возможности сохранить изменения без создания новой книги.

Как было показано выше, для более менее полноценной работы с excel файлами, в данном случае, нужно 4 библиотеки, и это не всегда удобно. Кроме этого, возможно нужен будет доступ к VBA (допустим для какой-либо последующей обработки) и с помощью этих библиотек его не получить.

Однако, работа с этими библиотеками достаточно проста и удобна для быстрого создания Excel файлов их форматирования, но если Вам надо больше возможностей, то следующий подпункт для Вас.

Работа с com-объектом

В своих отчетах я предпочитаю использовать второй способ, а именно использование файла Excel через com-объект с использованием библиотеки win32com. Его преимуществом, является то, что вы можете выполнять с файлом все операции, которые позволяет делать обычный Excel с использованием VBA.

Проиллюстрируем это на той же задаче, что и предыдущие примеры.

Для начала загрузим нужную библиотеку и создадим COM объект.

import win32com.client
Excel = win32com.client.Dispatch("Excel.Application")

Теперь мы можем работать с помощью объекта Excel мы можем получить доступ ко всем возможностям VBA. Давайте, для начала, откроем любую книгу и выберем активный лист. Это можно сделать так:

wb = Excel.Workbooks.Open(u'D:\Scripts\DataScience\ArticleScripts\ExcelPython\xl.xls')
sheet = wb.ActiveSheet

Давайте получим значение первой ячейки и последовательности:

#получаем значение первой ячейки
val = sheet.Cells(1,1).value

#получаем значения цепочки A1:A2
vals = [r[0].value for r in sheet.Range("A1:A2")]

Как можно заметить, мы оперируем здесь функциями чистого VBA. Это очень удобно если у вас есть написанные макросы и вы хотите использовать их при работе с Python при минимальных затратах на переделку кода.

Посмотрим, как можно произвести запись полученных значений:

#записываем значение в определенную ячейку
sheet.Cells(1,2).value = val

#записываем последовательность
i = 1
for rec in vals:
    sheet.Cells(i,3).value = rec
    i = i + 1

#сохраняем рабочую книгу
wb.Save()

#закрываем ее
wb.Close()

#закрываем COM объект
Excel.Quit()

Из примера видно, что данные операции тоже довольно просто реализовываются. Кроме этого, можно заметить, что изменения мы сохранили в той же книге, которую открыли для чтения, что достаточно удобно.

Однако, внимательный читатель, обратит внимание на переменную i, которая инициализируется не 0, как принято python, а 1. Это связано с тем, что мы работаем с индексами ячеек как из VBA, а там нумерация начинается не с 0, а с 1.

На этом закончим разбор способов работы с excel файлами в python и перейдем к обратной задаче.

Вызываем функции Python из MS Excel

Может возникнуть такая ситуация, что у вас уже есть какой-либо функция, которая обрабатывает данные на python, и нужно перенести ее функциональность в Excel. Конечно же можно переписать ее на VBA, но зачем?

Для использования функций python в Excel есть прекрасная надстройка ExcelPython. С ее помощью вы сможете вызывать функции написанные на python прямо из Excel, правда придется еще написать небольшую обертку на VBA, и все это будет показано ниже.

Итак, предположим у нас есть функция, написанная на python, которой мы хотим воспользоваться:

def get_unique(lists):
    sm = 0
    for i in lists:
        sm = sm + int(i.pop()) 
    return sm

На вход ей подается список, состоящий из списков, это одно из условий, которое должно выполняться для работы данной функции в Excel.

Сохраним функцию в файле plugin.py и положим его в ту же директорию, где будет лежать наш excel файл, с которым мы будем работать.

Теперь установим ExcelPython. Установка происходит через запуск exe-файла и не вызывает затруднений.

Когда все приготовления выполнены, открываем тестовый файл excel и вызовем редактор VBA (Alt+F11). Для работы с вышеуказанной надстройкой необходимо ее подключить, через Tools->References, как показано на рисунке:

Ну что же, теперь можно приступить к написанию функции-обертки для нашего Python-модуля plugin.py. Выглядеть она будет следующим образом:

Function sr(lists As Range)
    On Error GoTo do_error
        Set plugin = PyModule("plugin", AddPath:=ThisWorkbook.Path)
        Set result = PyCall(plugin, "get_unique", PyTuple(lists.Value2))
        sr = WorksheetFunction.Transpose(PyVar(result))
        Exit Function
do_error:
        sr = Err.Description
End Function

Итак, что же происходит в данной функции?

Для начала, с помощью PyModule, мы подключаем нужный модуль. Для этого в качестве параметров ей передается имя модуля без расширения, и путь до папки в которой он находится. На выходе работы PyModule мы получаем объект для работы с модулем.

Затем, с помощью PyCall, вызываем нужную нам функцию из указанного модуля. В качестве параметров PyCall получает следующее:

  1. Объект модуля, полученный на предыдущем шаге
  2. Имя вызываемой функции
  3. Параметры, передаваемые функции (передаются в виде списка)

Функция PyTuple, получает на вход какие-либо значения и преобразует их в объект tuple языка Python.
Ну и, соответственно, PyVar выполняет операцию преобразования результата функции python, к типу понятному Excel.

Теперь, чтобы убедиться в работоспособности нашей связки, вызовем нашу свежеиспеченую функцию на листе в Excel:

Как видно из рисунка все отработало правильно.

Надо отметить, что в данном материале используется старая версия ExcelPython, и на GitHub’e автора доступна новая версия.

Заключение

В качестве заключения, надо отметить, примеры в данной статье самые простые и для более глубоко изучения данных методов, я рекомендую обратиться к
документации по нужным пакетам.

Также хочу заметить, что указанные пакеты не являются единственными и в статье опущено рассмотрение, таких пакетов как xlsxwriter для генерации excel файлов или xlwings, который может работать с Excel файлами «на лету», а также же PyXLL, который выполняет аналогичные функции ExcelPython.

Кроме этого в статье я попытался несколько обобщить разборасанный по сети материал, т.к. такие вопросы часто фигурируют на форумах и думаю некоторым будет полезно иметь, такую «шпаргалку» под рукой.

Содержание

  1. Running Excel VBA from Python – pywin32
  2. Python code to call VBA procedure
  3. Need to know
  4. Download(s):
  5. Tested:
  6. Related link(s):
  7. Share this:
  8. Related
  9. 7 thoughts on “Running Excel VBA from Python – pywin32”
  10. Leave a Comment Cancel reply
  11. Python as a VBA Replacement В¶
  12. The Excel Object Model В¶
  13. Accessing the Excel Object Model in Python В¶
  14. Differences between VBA and Python В¶
  15. Case Sensitivity В¶
  16. Calling Methods В¶
  17. Named Arguments В¶
  18. Properties В¶
  19. Properties with Arguments В¶
  20. Implicit Objects and ‘With’ ¶
  21. Indexing Collections В¶
  22. Enums and Constant Values В¶
  23. Excel and Threading В¶
  24. Notes on Debugging В¶

Running Excel VBA from Python – pywin32

In wild cases it could happen that you would like to run Excel VBA from Python (or you put yourself in a position that you have to). The first time I run into this was when I realized that Excel and SQLite are not necessarily the best friends.

So I found myself in a situation where there was a nice Excel report template file enhanced with some VBA, but data required for the report was in SQLite. I clapped myself on the shoulder, since to create the report:

  1. need to run a python app to get the data
  2. save the data
  3. open the template file
  4. copy the data to the template file
  5. run the VBA code

and last but not least save the template with a different file name. Sounds time consuming? It is. And I am lazy 🙂 . Luckily since then I improved my let’s say “tailor-made” reporting system so all of the steps are performed with Python.

Now let’s focus on how you can call Excel VBA procedures in Python.

Python code to call VBA procedure

Need to know

  • when you are opening the workbook you have to use the absolute path, relative path will not be enough, even if you are storing your Excel files somewhere in your Python project folder
  • the next step is executing the VBA script (xl.Application.run) at this point you only need the actual Excel file name and you have to refer to your macro as:

your_Excel_file_name ! VBA_module_name . VBA_procedure_name

Created a very simple example for presentation purposes only (simpleMacroForPython.xlsm) download and play around with it:

Enter file path and press enter:

Open the Excel file, you can see the last update time and the updater user ID:

Download(s):

Tested:

    • Windows 10/Office 2016 – 64-bit
    • Python 3.7.2

7 thoughts on “Running Excel VBA from Python – pywin32”

I have a macro written in Excel that allows me to save a worksheet to a CSV. I want to use Python to automate the running of this macro daily because the resulting CSV feeds into an ETL job.

Here is the VBA:
Sub SaveToCSV()

Dim FileName As String
Dim PathName As String
Dim ws As Worksheet

Kill “C:UsersU6049119OneDrive – Clarivate AnalyticsProjectsQualityEMEA Quality Data.csv”

Set ws = ActiveWorkbook.Sheets(“Tableau Data”)
File = “EMEA Quality Data.csv”
PathName = “C:UsersU6049119OneDrive – Clarivate AnalyticsProjectsQuality”
ws.Copy
ActiveWorkbook.SaveAs FileName:=PathName & “” & File, _
FileFormat:=xlCSVUTF8, CreateBackup:=False

Workbooks(“EMEA Quality Data.csv”).Close
Workbooks(“EMEA Quality Check Form.xlsm”).Close

Here is the Python Script:
>>> def DailyQualitySave():
… xl = win32.Dispatch(‘Excel.Application’)
… xl.Application.visible = False
… try:
… wb = xl.Workbooks.Open(os.path.abspath(“C:UsersU6049119OneDrive – Clarivate AnalyticsProjectsQualityEMEA Quality Check Form.xlsm”))
… xl.Application.run(“EMEA Quality Check Form.xlsm!Module4.SaveToCSV”)
… wb.Save()
… wb.Close()
… except Exception as ex:
… template = “An exception of type <0>occured. Arguments:n<1!r>”
… message = template.format(type(ex).__name__, ex.args)
… print (message)
… xl.Application.Quit()
… del xl

When I attempt to run the script I get the following error:
An exception of type com_error occured. Arguments:
(-2147352567, ‘Exception occurred.’, (0, u’Microsoft Excel’, u”Cannot run the macro ‘EMEA Quality Check Form.xlsm!Module4.SaveToCSV’. The macro may not be available in this workbook or all macros may be disabled.”, u’xlmain11.chm’, 0, -2146827284), None)

Is there a way to enable macros within the python script?

There are spaces in your Excel file name try to use single quotes (‘) around the file name:
xl.Application.run(«‘EMEA Quality Check Form.xlsm’!Module4.SaveToCSV»)

Hi,
First of all thanks for share this kind of usefull information with us.
Now, we have total 6000 records in a xls and processed(diffrent mathematical calculation) through macro.
As the macro have huge line of codes , it will take time to migrate whole code to python.
So we use this process to better the performance.
But after some time the processed got failed and xls got blank out.
We had tried with small sample of data and it working fine.
Please help me to resolve this and increase the performance using multi threding or multi processing.

An exception of type com_error occurred. Arguments:
(-2147352567, ‘Exception occurred.’, (0, ‘Microsoft Excel’, “Cannot run the macro ‘AmberHoursCalculator-HeatmapMasterSheetv1.8.xlsm!lookupLoop.Open_Report’. The macro may not be available in this workbook or all macros may be disabled.”, ‘xlmain11.chm’, 0, -2146827284), None)

Getting the above error when trying to run the above code:

followed the instructions perfectly and I know the macros runs.

Hi, without having a look at the code you are using it is not possible to figure out the reason, it might be something with the file/macro path (an extra quote sign or space) or it might be something else completely. My suggestion without any further information is to try to call a simple macro (e.g. a ‘Hello world’) and the filename should not consist any ‘-‘ or spaces. Or you can try to run your macro with xlwings: Running Excel VBA – Python – xlwings

Thank you for that post, this really helps. However, I am working with Excel files, which are validated by a macro. The information of the validation is shown in a message window. For instance, the error number is used in the window title, some detailed information in the main part of this message window. Is there a way to grab this information during or after calling the macro?

Alternatively: Is there a way to send the data used in the message window to python? (e.g. by changing or expanding the vba code)?

Thanks for your advice!

Hi Sebastian,
Off the top of my head I would suggest to use a log file in order to capture validation results. (https://www.exceltip.com/files-workbook-and-worksheets-in-vba/log-files-using-vba-in-microsoft-excel.html
To be honest I have never tried to send info from VBA to python :). Cheers

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Источник

Python as a VBA Replacement В¶

Everything you can write in VBA can be done in Python. This page contains information that will help you translate your VBA code into Python.

Please note that the Excel Object Model is part of Excel and documented by Microsoft. The classes and methods from that API used in this documentation are not part of PyXLL, and so please refer to the Excel Object Model documentation for more details about their use.

The Excel Object Model В¶

When programming in VBA you interact with the Excel Object Model. For example, when writing

what you are doing is constructing a Range object and calling the Select method on it. The Range object is part of the Excel Object Model.

Most of what people talk about in reference to VBA in Excel is actually the Excel Object Model, rather than the VBA language itself. Once you understand how to interact with the Excel Object Model from Python then replacing your VBA code with Python code becomes straightforward.

The Excel Object Model is well documented by Microsoft as part of the Office VBA Reference.

The first hurdle people often face when starting to write Excel macros in Python is finding documentation for the Excel Python classes. Once you realise that the Object Model is the same across Python and VBA you will see that the classes documented in the Office VBA Reference are the exact same classes that you use from Python, and so you can use the same documentation even though the example code may be written in VBA.

Accessing the Excel Object Model in Python В¶

The Excel Object Model is made available to all languages using COM. Python has a couple of packages that make calling COM interfaces very easy. If you know nothing about COM then there’s no need to worry as you don’t need to in order to call the Excel COM API from Python.

The top-level object in the Excel Object Model is the Application object. This represents the Excel application, and all other objects are accessed via this object.

PyXLL provides a helper function, xl_app , for retrieving the Excel Application object. By default, it uses the Python package win32com , which is part of the pywin32 package [1].

If you don’t already have the pywin32 package installed you can do so using pip :

Or if you are using Anaconda you can use conda :

You can use xl_app to access the Excel Application object from an Excel macro. The following example shows how to re-write the Macro1 VBA code sample from the section above.

Note that in VBA there is an implicit object, which related to where the VBA Sub (macro) was written. Commonly, VBA code is written directly on a sheet, and the sheet is implied in various calls. In the Macro1 example above, the Range is actually a method on the sheet that macro was written on. In Python, we need to explicitly get the current active sheet instead.

You can call into Excel using the Excel Object Model from macros and menu functions, and use a sub-set of the Excel functionality from worksheet functions, where more care must be taken because the functions are called during Excel’s calculation process.

You can remove these restrictions by calling the PyXLL schedule_call function to schedule a Python function to be called in a way that lets you use the Excel Object Model safely. For example, it’s not possible to update worksheet cell values from a worksheet function, but it is possible to schedule a call using schedule_call and have that call update the worksheet after Excel has finished calculating.

For testing, it can also be helpful to call into Excel from a Python prompt (or a Jupyter notebook). This can also be done using xl_app , and in that case the first open Excel instance found will be returned.

You might try this using win32com directly rather than xl_app . We do not advise this when calling your Python code from Excel however, as it may return an Excel instance other than the one you expect.

Differences between VBA and Python В¶

Case Sensitivity В¶

Python is case sensitive. This means that code fragments like r.Value and r.value are different (note the capital V in the first case. In VBA they would be treated the same, but in Python you have to pay attention to the case you use in your code.

If something is not working as expected, check the PyXLL log file. Any uncaught exceptions will be logged there, and if you have attempted to access a property using the wrong case then you will probably see an AttributeError exception.

Calling Methods В¶

In Python, parentheses ( () ) are always used when calling a method. In VBA, they may be omitted. Neglecting to add parentheses in Python will result in the method not being called, so it’s important to be aware of which class attributes are methods (and must therefore be called) and which are properties (whose values are available by reference).

For example, the method Select on the Range type is a method and so must be called with parentheses in Python, but in VBA they can be, and usually are, omitted.

Keyword arguments may be passed in both VBA and Python, but in Python keyword arguments use = instead of the := used in VBA.

Accessing properties does not require parentheses, and doing so will give unexpected results! For example, the range.Value property will return the value of the range. Adding () to it will attempt to call that value, and as the value will not be callable it will result in an error.

Named Arguments В¶

In VBA, named arguments are passed using Name := Value . In Python, the syntax is slightly different and only the equals sign is used. One other important difference is that VBA is not case-sensitive but Python is. This applies to argument names as well as method and property names.

In VBA, you might write

If you look at the documentation for Application.InputBox you will see that the argument names are cased different from this, and are actually ‘Prompt’ and ‘Type’. In Python, you can’t get away with getting the case wrong like you can in VBA.

In Python, this same method would be called as

Properties В¶

Both VBA and Python support properties. Accessing a property from an object is similar in both languages. For example, to fetch ActiveSheet property from the Application object you would do the following in VBA:

In Python, the syntax used is identical:

Properties with Arguments В¶

In VBA, the distinction between methods and properties is somewhat blurred as properties in VBA can take arguments. In Python, a property never takes arguments. To get around this difference, the win32com Excel classes have Get and Set methods for properties that take arguments, in addition to the property.

The Range.Offset property is an example of a property that takes optional arguments. If called with no arguments it simply returns the same Range object. To call it with arguments in Python, the GetOffset method must be used instead of the Offset property.

The following code activates the cell three columns to the right of and three rows down from the active cell on Sheet1:

To convert this to Python we must make the following changes:

  • Replace the Offset property with the GetOffset method in order to pass the arguemnts.
  • Replace rowOffset and columnOffsetRowOffset and ColumnOffset as specified in the Range.Offset documentation.
  • Call the Activate method by adding parentheses in both places it’s used.

You may wonder, what would happen if you were to use the Offset property in Python? As you may by now expect, it would fail — but not perhaps in the way you might think.

If you were to call xl.ActiveCell.Offset(RowOffset=3, ColumnOffset=3) the the result would be that the parameter RowOffset is invalid. What’s actually happening is that when xl.ActiveCell.Offset is evaluated, the Offset property returns a Range equivalent to ActiveCell, and that Range is then called.

Range has a default method. In Python this translates to the Range class being callable, and calling it calls the default method.

The default method for Range is Item, and so this bit of code is actually equivalent to xl.ActiveCell.Offset.Item(RowOffset=3, ColumnOffset=3) . The Item method doesn’t expect a RowOffset argument, and so that’s why it fails in this way.

Implicit Objects and ‘With’ ¶

When writing VBA code, the code is usually written ‘on’ an object like a WorkBook or a Sheet. That object is used implicitly when writing VBA code.

If using a ‘With..End’ statement in VBA, the target of the ‘With’ statement becomes the implicit object.

If a property is not found on the current implicit object (e.g. the one specified in a ‘With..End’ statement) then the next one is tried (e.g. the Worksheet the Sub routine is associated with). Finally, the Excel Application object is implicitly used.

In Python there is no implicit object and the object you want to reference must be specified explicitly.

For example, the following VBA code selects a range and alters the column width.

To write the same code in Python each object has to be referenced explicitly.

Indexing Collections В¶

VBA uses parentheses ( () ) for calling methods and for indexing into collections.

In Python, square braces ( [] ) are used for indexing into collections.

Care should be taken when indexing into Excel collections, as Excel uses an index offset of 1 whereas Python uses 0. This means that to get the first item in a normal Python collection you would use index 0, but when accessing collections from the Excel Object Model you would use 1.

Enums and Constant Values В¶

When writing VBA enum values are directly accessible in the global scope. For example, you can write

In Python, these enum values are available as constants in the win32com.client.constants package. The code above would be re-written in Python as follows

Excel and Threading В¶

In VBA everything always runs on Excel’s main thread. In Python we have multi-threading support and sometimes to perform a long running task you may want to run code on a background thread.

The standard Python threading module is a convenient way to run code on a background thread in Python. However, we have to be careful about how we call back into Excel from a background thread. As VBA has no ability to use threads the Excel objects are not written in a such a way that they can be used across different threads. Attempting to do so may result in serious problems and even cause Excel to crash!

In order to be able to work with multiple threads and still call back into Excel PyXLL has the schedule_call function. This is used to schedule a Python function to run on Excel’s main thread in such a way that the Excel objects can be used safely. Whenever you are working with threads and need to use the Excel API you should use schedule_call .

For example, you might use an Excel macro to start a long running task and when that task is complete write the result back to Excel. Instead of writing the result back to Excel from the background thread, use schedule_call instead.

Notes on Debugging В¶

The Excel VBA editor has integrating debugging so you can step through the code and see what’s happening at each stage.

When writing Python code it is sometimes easier to write the code outside of Excel in your Python IDE before adapting it to be called from Excel as a macro or menu function etc.

When calling your code from Excel, remember that any uncaught exceptions will be printed to the PyXLL log file and so that should always be the first place you look to find what’s going wrong.

If you find that you need to be able to step through your Python code as it is being executed in Excel you will need a Python IDE that supports remote debugging. Remote debugging is how debuggers connect to an external process that they didn’t start themselves.

You can find instructions for debugging Python code running in Excel in this blog post Debugging Your Python Excel Add-In.

Источник

I will explain how to call an Excel VBA macro from Python as a practical use of UDF in xlwings. For those who are asking «What is xlwings?», Please see the post below from the installation of Python to the basic usage of UDF of xlwings.
Introduction to Python for VBA users-Calling Python from Excel with xlwings-

1. Background

Even if you start making in-house EUC tools with Python aiming for Excel VBA, I think that there are a lot of Excel VBA tools made by our predecessors in the company and you have to use them as well. I will. I can’t just throw it away just because I hate VBA, and even if I try to rewrite it in Python, I’m too busy to do that. In the end, I wonder if I can escape from VBA.

And when using multiple Excel VBA tools in a series of work, I usually open a file, execute a macro, and when finished, the next file. Then, it may be better to create a program in VBA that automatically executes VBA macros one after another.

No, let’s stop VBA. You can do it with Python.

This article describes how to run an Excel VBA macro from Python. This way you can comfortably code in Python while leveraging your existing VBA assets.

2. Environment

I am trying in the following environment.

  • OS: Windows 10 version 1909
  • Python: version 3.7.5
  • xlwings: version 0.18.0
    —Editor: VSCode version 1.45.1 & Python extension version 2020.5.80290

3. Basic

Try calling an Excel VBA macro from Python by referring to Official document (Python API) Let’s do it.

In a folder of your choice, create a file called VBA_example.xlsm and open the VBA editor with ʻAlt + F11`. Add an appropriate module (let’s call it Module1 for the time being) and write the following.

VBA_example.xlsm!Module1


Sub hello_VBA()
    MsgBox "Hello VBA from Python."
End Sub

Then open the same folder in Visual Studio Code and create a file called call_VBA.ipynb. Do the following (preferably line by line) with call_VBA.ipynb:

call_VBA.ipynb


import xlwings as xw             # 1.Import xlwings
wb = xw.Book('VBA_example.xlsm') # 2.Open the book
macro = wb.macro('hello_VBA')    # 3.Get macro
macro()                          # 4.Run macro

To explain the code

  1. Import xlwings. Since it is ʻas xw, call xlwings with xw` below.
  2. If the book is in the same folder, you can open it with xw.Book.
  3. Store the macro hello_VBA in the workbook in the variable macro. If you’re a VBA user, you might get a ?? here, but Python can also store functions in variables.
  4. Run macro. In VBA, it may be Call macro, but Call is unnecessary. For macros that require arguments, put the arguments in parentheses.

It will be.

Here’s a screen running this in VS Code:
ipynb実行画面.png

It stops when I run the last cell, but when I activate the Excel window, I get the following screen:
Hello VBA.png

The VBA macro is being executed properly.

4. Application (continuous execution of VBA macro)

This is a practical example using UDF. Let’s try to execute VBA macros of other Excel files continuously from the button of one Excel file.

Create VBA_caller.xlsm in a folder of your choice. The contents of the worksheet should look like this:
VBA_caller.png

—The sheet name is VBA_caller
—Create table T.VBA_caller (from formatting as home tab table. Location is OK.)
—The table should contain columns folders, files, and macros.

Open the VBA editor screen with ʻAlt + F11` and check xlwing in the reference settings. Create Module1 and paste the following:

VBA_caller.xlsm!Module1


Sub main()
  Dim rg_target As Range
  Set rg_target = Worksheets("VBA_caller").ListObjects("T.VBA_caller").Range
  
  Application.DisplayAlerts = False
  var_log = xlwings_udfs.call_vba(rg_target)
  Application.DisplayAlerts = True
End Sub

Prepare a Python script. Create a file VBA_caller.py with the following contents in the same folder.

VBA_caller.py


import os
from pathlib import Path

import numpy as np
import pandas as pd
import xlwings as xw


os.chdir(Path(__file__).parent.absolute().resolve())

@xw.sub
@xw.arg('df_target', pd.DataFrame, index=False, header=True)
def call_vba(df_target: pd.DataFrame):

    def call_vba_single(row):
        #Read settings
        wb_path, wb_name, macro_name = row['folder'], row['File'], row['macro']

        #Create an Excel instance for macro execution and open the workbook
        app = xw.apps.add()
        xl_path  = (Path() / wb_path / wb_name).resolve()
        wb = app.books.open(str(xl_path))

        #Macro execution
        app.display_alerts = False #During macro execution, Excel displays the message "Wait for another program to complete the OLE operation." And the execution stops.
        wb.macro(macro_name)()
        app.display_alerts = False #Set to False again as it may be returned to True at the callee

        #Save the workbook and kill the Excel instance for macro execution
        wb.save() 
        wb.close()
        app.kill()

    for _, row in df_target.iterrows():
        call_vba_single(row)

@xw.sub
def kill_excel():
    os.system('taskkill /f /im excel.exe')

#For debugging
if __name__ == '__main__':
    xw.serve()

After creating VBA_caller.py, go back to VBA_caller.xlsm and press the ʻImport Functions button on the xlwings tab to import the contents of VBA_caller.py` as UDF. This completes the caller’s preparation.

Let’s also prepare the called Excel file for the experiment.

Folder structure


┌example1
│ └example1.xlsm
├example2
│ ├example2.xlsm
│ └example3
│   └example3.xlsm
├VBA_caller.py
└VBA_caller.xlsm

For ʻexample1.xlsm to ʻexample3.xlsm, save the following in the standard module:

VB:example1.slsm~example3.xlsm!Module1


'Change procedure-name and MsgBox string accordingly
Sub example1()
    MsgBox "example1"
End Sub

You are now ready.

I will try it. At VBA_caller.xlsm, press ʻAlt + F8, select main and press the Run button. If ʻexample1.xlsm opens in the new Excel instance and ʻexample1` is displayed in the message box, it is successful. When you press the OK button, the macros of example2.xlsm and example3.xlsm will be executed one after another.

5. Further application (simultaneous execution of VBA macros)

With the contents so far, you can do almost the same with VBA. However, if you rewrite VBA_caller.py a little, you can execute multiple macros at the same time.

Fix 1: Add the following to the end of the imoprt statement.

VBA_caller.py


import joblib

Fix # 2: Remove the following for loop and

VBA_caller.py


    for _, row in df_target.iterrows():
        call_vba_single(row)

Change to a call using joblib.

VBA_caller.py


    joblib.Parallel(n_jobs=-1, verbose=10)([
        joblib.delayed(call_vba_single)(row) for _, row in df_target.iterrows()
    ])

Fix (?) Part 3: (Try it if it doesn’t work up to 2) Enter the path of python.exe in ʻInterpreteron the xlwings tab. If you have Anaconda installed with default settings, it will beC: ProgramData Anaconda3 python.exe`.

Go back to VBA_caller.xlsm and try running main again from ʻAlt + F8`. This time, multiple Excels will be launched at the same time and the macros will be executed at the same time. This is (probably) not possible with VBA, and you can reduce the overall processing time by running multiple macros at the same time.

joblib is a library that allows you to easily perform parallel processing in Python. For a detailed explanation, see here.

in conclusion

Thank you for reading to the end.
This time, I didn’t write about calling VBA macros that require arguments because it is a basic usage, but I would appreciate it if you could comment to that effect.

reference

-Official Document
-Introduction to Python for VBA users-Calling Python from Excel with xlwings-

Понравилась статья? Поделить с друзьями:
  • Excel vba formular1c1 with if
  • Excel vba formula если
  • Excel vba formula value
  • Excel vba format функция
  • Excel vba format yyyy