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.
See also Introduction.
When programming in VBA you interact with the Excel Object Model. For example, when writing
Sub Macro1() Range("B11:K11").Select EndSub 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.
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:
pip install pywin32 Or if you are using Anaconda you can use conda:
conda install pywin32 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.
from pyxll import xl_macro, xl_app @xl_macro def macro1(): xl = xl_app() # 'xl' is an instance of the Excel.Application object # Get the current ActiveSheet (same as in VBA) sheet = xl.ActiveSheet # Call the 'Range' method on the Sheet xl_range = sheet.Range('B11:K11') # Call the 'Select' method on the Range. # Note the parentheses which are not required in VBA but are in Python. xl_range.Select() 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.
from win32com.client.gencache import EnsureDispatch # Get the first open Excel.Application found, or launch a new one xl = EnsureDispatch('Excel.Application') 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.
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.
' Select is a method and is called without parentheses in VBA Range("B11:K11").Select from pyxll import xl_app xl = xl_app() # In Python, the parentheses are necessary to call the method xl.Range('B11:K11').Select() 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.
from pyxll import xl_app xl = xl_app() # Value is a property and so no parentheses are used value = xl.Range('B11:K11').Value 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
Set myRange = Application.InputBox(prompt := "Sample", type := 8) 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
from pyxll import xl_app xl = xl_app() my_range = xl.InputBox(Prompt='Sample', Type=8) 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:
Set mySheet = Application.ActiveSheet In Python, the syntax used is identical:
from pyxll import xl_app xl = xl_app() my_sheet = xl.ActiveSheet 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:
Worksheets("Sheet1").Activate ActiveCell.Offset(rowOffset:=3, columnOffset:=3).Activate 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 columnOffset RowOffset and ColumnOffset as specified in the Range.Offset documentation.
Call the Activate method by adding parentheses in both places it’s used.
from pyxll import xl_app xl = xl_app() xl.Worksheets('Sheet1').Activate() xl.ActiveCell.GetOffset(RowOffset=3, ColumnOffset=3).Activate() Note
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.
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.
Sub Macro2() ' ActiveSheet is a property of the Application Set ws = ActiveSheet With ws ' Range is a method of the Sheet Set r = Range("A1:B10") ' Call Select on the Range r.Select End With ' Selection is a property of the Application Selection.ColumnWidth = 4 End Sub To write the same code in Python each object has to be referenced explicitly.
from pyxll import xl_macro, xl_app @xl_macro def macro2(): # Get the Excel.Application instance xl = xl_app() # Get the active sheet ws = xl.ActiveSheet # Get the range from the sheet r = ws.Range('A1:B10') # Call Select on the Range r.Select() # Change the ColumnWidth property on the selection xl.Selection.ColumnWidth = 4 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.
When writing VBA enum values are directly accessible in the global scope. For example, you can write
Set cell = Range("A1") Set cell2 = cell.End(Direction:=xlDown) 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
from pyxll import xl_app from win32com.client import constants xl = xl_app() cell = xl.Range('A1') cell2 = cell.End(Direction=constants.xlDown) 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.
from pyxll import xl_macro, xl_app, schedule_call import threading @xl_macro def start_task(): # Here we're being called from a macro on the main thread # so it's safe to use pyxll.xl_app. xl = xl_app() value = float(xl.Selection.Value) # Use a background thread for a long running task. # Be careful not to pass any Excel objects to the background thread! thread = threading.Thread(target=long_running_task, args=(value,)) thread.start() # This runs on a background thread def long_running_task(value): # Do some work that takes some time result = ... # We shouldn't write the result back to Excel here as we are on # a background thread. Instead use pyxll.schedule_call to write # the result back to Excel. schedule_call(write_result, result, "A1") # This is called via pyxll.schedule_call def write_result(result, address): # Now we're back on the main thread and it's safe to use pyxll.xl_app xl = xl_app() cell = xl.Range(address) cell.Value = result 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.