Passing arrays from VBA to Python (And back again) - python

I want to send an array of decimal numbers to python from VBA and return a set of them. Currently I can run a python script from a VBA macro and pass it string arguments but I can't send arrays.
I've looked into other ways to do this but can't seem to get any other way working. Also I'm trying to stay away from paid software like PyXll or anything similar.
I'm much more familiar with Python then VBA so I would preferer to work in Python and just use VBA to send and receive the data for the CSV file
This is what I've got so far,
Pythonexe = """C:\ ~~~ \python.exe""" 'path of the python.exe
PythonScript = """C:\ ~~~ \ExcelToPython.py""" 'path of the Python script
Dim ColumnLength As Integer
Dim RowLength As Integer
Dim counter As Integer
counter = 0
For RowLength = 0 To 10
For ColumnLength = RowCounter
PythonArg(counter) = (ThisWorkbook.Worksheets("location sort").Cells(ColumnLength, RowLength))
counter = counter + 1
Next ColumnLength
objShell.Run Pythonexe & PythonScript & PythonArg
counter = 0
Next RowLength
I'm trying to get this as simple as possible as I will be ran on several different machines.
Any help would be appreciated.

Related

Apply Worksheet_Change, to cell updated by Python code, to copy the updated cell data to another worksheet

I have the following script to fetch dynamically changing data in cell F60 on a worksheet. It is a stock price which constantly changes. This data is fetched by a Python code and is working.
I need to record this F60 data on another sheet within the same workbook every time it changes, without deleting the previously recorded data.
When I enter values manually in F60, I see all these values one by one on the destination sheet.
If the data is updated automatically (by Python code), I see the time stamp and not the data.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
If Not Intersect(Target, Range("F60")) Is Nothing Then
Dim x
Dim NR As Long
With Application
.EnableEvents = False
x = Target.Value
On Error Resume Next
With Sheets("Sheet2")
NR = .Range("A" & Rows.Count).End(xlUp).Row + 1
.Range("A" & NR).Value = Now
.Range("B" & NR).Value = x
End With
On Error GoTo 0
.EnableEvents = True
End With
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
Let me post my comment as answer:
The Worksheet_Change-event is triggered only if data is modified within Excel (the program itself), either because a user typed or because a VBA code wrote something into one or more cells.
It can't be triggered if you write it via Python (or any other program). Python doesn't open (the Application) Excel and therefore no VBA environment is available. VBA needs it's host program (in your case that's Excel) to run. If Python writes the data, it also needs to write the data into your "history" sheet.
Think of it vice versa: You have a text file (or any other file). If you write something into this file using Excel/VBA, you can't expect that a piece of Python code runs automatically and does something with the data.

How do I send/read data from VBA in Python?

Background
Right now I'm creating a macro to help automate the creation of some graphs in VBA. However, the creation of the graphs requires specific tasks to be done, for example, certain points in a series to be larger depending on previous instances. I would much rather do this data manipulation in python.
Problem
I want to use excel for its user-friendly interface but want to handle all the data manipulation within Python. How can I send data I create in VBA to python. To clarify I'm not trying to read specific cells in the excel sheet.
If I define a string in VBA say...
Dim example_string as String
example_string = "Hello, 1, 2, 3, Bye"
How can I send this information I created within VBA to Python for manipulation?
More Specifics
I have a textbox in excel that is filled by the user, which I read using VBA. I want to send that txt data from VBA to python. The user highlights the desired cells, which are not necessarily the same each time, clicks a button and fills a textbox. I don't want to use range or specific cell selection since this would require the user to specifically enter all the desired data into cells (too time-consuming).
I want to understand the basic procedure of how to send data between VBA and python.
You can do the whole thing in python, it will be more efficient and you can either use excel or sqlite3 as database, go here to read about graphic interfaces with tkinter, use pandas and numpy to process your data.
If you insist in sending data to python, import sys to your python script to read parameters and then run it from vba with the shell() method.
EDIT: You wanted an example, here it is =>
Open a new excel file, create a procedure like this (VBA CODE):
Sub sendToPython()
Dim shell As Object
Dim python As String
Dim callThis As String
Dim passing
Set shell = VBA.CreateObject("Wscript.Shell")
'/* This is where you installed python (Notice the triple quotes and use your own path *always)*/
python = """C:\Users\yourUserName\appdata\local\programs\python\python37\python.exe"""
'/* This is the data you'll be passing to python script*/
passing = "The*eye*of*the*tiger"
callThis = "C:\Users\yourUserName\desktop\yourScriptName.py " & passing & ""
shell.Run python & callThis
End Sub
The idea is to create some kind of a parser in python, this is my silly example (PYTHON CODE):
import sys
f = open("log.txt", "w")
arg = (sys.argv[1]).split("*")
s = " "
arg = s.join(arg)
print("This is the parameter i've entered: " + arg, file=f)
Notice how i used sys to read a parameter and i exported to actually see some results because otherwise you'll just see a black screen popping up for like a millisecond.
I also found this article, but it requires you to wrap the python script in a class and i don't know if that works for you

Run multiple python scripts, that use user inputs and while loops, simultaneously

Basically I need to run three files simultaneously and independently. These files are started with a user input followed by an infinite While Loop.
I have found some questions similar to mine but the solutions do not quite fit my needs. I am still a beginner.
I have already tried:
python device1.py &
python device2.py &
python device3.py
I also tried doing this all in one file but the file is rather large and complicated, and have not succeeded thus far.
#some code that creates a csv
#input
device = input("input which device you want to connect to")
def function():
#write to csv file from data
while True:
#get live data from device
#csv function
function()
I expect to enter 3 inputs for my 3 scripts, they run their loops, I end the code and have 3 csv files.
Have you tried setting the input in your command?
echo inputForDevice1 | python device1.py &
echo inputForDevice2 | python device2.py &
echo inputForDevice3 | python device3.py &
Also remember to detach from the last python call (python device3.py &), otherwise you'll be stuck in the infinite loop.

VBScript kills program before text file is made

I made a vbscript to open an excel doc, then runs a python program that pulls data from the documents tables and prints it to a text file. The script is supposed to wait until the python program is done creating the text doc then close the excel doc, but for whatever reason my python program closes before it even has a chance to make that text doc.
I even changed the python code to just print a simple 'Hello World' into a new text document in case pulling data from excel was causing problems but the text document still wasn't created.
This is the script that i'm running:
Set xl = CreateObject("Excel.application")
xl.Application.Workbooks.Open "C:\Users\V\Documents\_PROGRAMS_\TEST.xlsx"
xl.Application.Visible = True
Dim oshell
Set oshell = WScript.CreateObject("WScript.Shell")
oshell.CurrentDirectory = "C:\Users\V\Documents\_PROGRAMS_\"
windowStyle = 1
waitUntilFinished = True
oshell.run "python table.py", windowStyle, waitUntilFinished
xl.Application.Quit
I don't think adding the python program is important since that isn't really the problem. Although I will say that I tried putting a delay in the python program to see if that would change anything (it didn't).
I though adding the two extra arguments to .run would make it wait until the process is finished but I guess I must be missing something?
I'm just starting to learn how to use vbscript so any explanations of code would be welcomed!
Thanks!
EDIT: So after more testing it seems that it does have something to do with accessing the excel document, as just printing 'Hello World' to a file did actually work and the file was created (I made it in the wrong directory by accident so I was looking in the wrong place). But trying it with the data from the excel document no file is created, the program just ends
So here's the python code I wrote:
#!/usr/bin/python27
import pandas as pd
table = pd.read_excel("TEST.xlsx") #Get excel doc
file = open("text.txt", "w") #Open new file
file.write(table.columns.values) #Print out column headers
file.write("Hello!")
file.close()

Why is the return result of ExecuteExcel4Macro always False?

While using ExecuteExcel4Macro to run a Excel macro in Python, I always get the False result, here is the code executed:
import win32com.client
filename = r'E:\excel.xls'
xlApp = win32com.client.Dispatch('Excel.Application')
xlApp.visible = 1
xlBook = xlApp.Workbooks.Open(filename)
strPara = xlBook.Name + '!Macro1()'
res = xlApp.ExecuteExcel4Macro(strPara)
print res
xlBook.Close(SaveChanges=0)
and the output of "print res" statement is: False
After I search the usage of ExecuteExcel4Macro on MSDN, I get the following information:
ExecuteExcel4Macro -- Runs a Microsoft Excel 4.0 macro function and then returns the result of the function. The return type depends on the function.
Then I get confused: since macro in Excel is always a "Sub procedure" and a "Sub procedure" in VBA has no return result, how can a Excel macro return a result? Then what does the False result in the above example stand for?
After that, I try ExecuteExcel4Macro within Excel(2003) by coding not in Python but in VBA:
Sub RunMacro()
res = ExecuteExcel4Macro("excel.xls!Macro1()")
MsgBox CStr(res)
End Sub
Sub Macro1()
MsgBox "in Macro1"
End Sub
and the "res" string shown in MsgBox is the same: False
1.Why is the return result of ExecuteExcel4Macro always False?
2.What should I do if I want to run an Excel macro in Python and to get the exit status of the Excel macro function?
Updated at 2011.10.28:
Sub TEST()
res = Application.Run(MacroToRun)
MsgBox CStr(res)
End Sub
Function MacroToRun()
MacroToRun = True
End Function
After I run TEST Macro in Excel 2003, I get this:
A dialog with the information "Error 2015".
You may define Excel functions, not by starting with sub but with function:
Function Area(Length As Double, Optional Width As Variant)
If IsMissing(Width) Then
Area = Length * Length
Else
Area = Length * Width
End If
End Function
This should return something. This something is the content of the variable named after the function (here Area).
You can call a VBA UDF using this Excel 4 Macro with the ExecuteExcel4Macro method:
retval = Application.ExecuteExcel4Macro("EVALUATE(""Book1!some_UDF()"")")
The use of the Excel 4 Macro Function EVALUATE() won't allow you to step into the UDF named "some_UDF" in VBE debug mode as you could if you had called the UDF from an Excel 4 Macro (see Example).
I have also noticed that Application.Caller cannot be mentioned at all in the UDF, otherwise Excel crashes.
Example
Excel 4 Macro Sheet with the following (cell $A$1 being a named "Macro1" via Name Manager):
$A$1: Macro1
$A$2: =RETURN(Book1!some_UDF())
Excel Worksheet in cell
$A$1: =Macro1()
As a conclusion, I suspect this Example to works correctly only because Excel uses its own Application.Evaluate to get the result of the VBA UDF (lets say integer 10), and then running the Excel 4 Macro as if it where:
$A$1: Macro1
$A$2: =RETURN(10)
Indeed, prior to the VBA era, in 1992, when there was only Excel 4.0 Macro Functions, =RETURN(Book1!some_UDF()) could not possibly work, its inconceivable (not verified but I can't see how it could works...)
So to have exactly the same behavior in VBA only, the call would be:
retval = Application.ExecuteExcel4Macro("EVALUATE(" & Application.Run("Book_XL4M!test_10") & ")")
Some additional information from the year 2020.
N.B.: I am using Excel Pro plus 2016
This comment is just here to save someone else the pain and lost time I have gone through. This maybe obvious to some programmers but I didn't know nor could I find anything on the internet advising me of the problem/restriction.
After a lot of painful work I have discovered you cannot run ExecuteExcel4Macro within a function(UDF). Also worked out you cannot call a sub that runs ExecuteExcel4Macro form within a function(UDF). It just ended the function at the ExecuteExcel4Macro and returned #Value! in the cell. I did not have an error capture system running. This may be the issue with the above problem but I'm not sure if there is a different result on older excel.
Background Info:
I have a cell I wanted to test if the user had changed the colour or if the conditional formatting had changed the colour. I wanted to know what colour was showing on the screen.
I tried a function (UDF) but had an issue with .displayformat. You cannot use .displayformat in a function.
So I thought I could use the Get.Cell(63,cell). To do that I needed to use ExecuteExcel4Macro but it would not work in a function(UDF). It would crash the function with Error2015. However it did work in a macro by itself.
I was using a named range with a formula of =GET.CELL(63,INDIRECT("rc",FALSE)) for the conditional formatting and this worked without incident.
Some of the code I was using to test in a function(UDF). After TempA was defined I would jump to Temp = ...
TempA = Application.ExecuteExcel4Macro("Sqrt(4)")
TempA = Application.ExecuteExcel4Macro("GET.CELL(42)")
TempA = "GET.CELL(63,HistorySheet!" & MyCell.Address(ReferenceStyle:=xlR1C1) & ")"
Temp = ExecuteExcel4Macro(TempA)
The issue of ExecuteExcel4Macro in a function(UDF) may be obvious to some programmers but I didn't know nor could I find anything on the internet let me know of the problem/restriction. Hoping this will help someone in the future

Categories

Resources