【问题标题】:Python code for running ms-Access Module Subroutine用于运行 ms-Access 模块子例程的 Python 代码
【发布时间】:2015-12-15 16:59:07
【问题描述】:

我对编程很陌生,这是我关于 stackoverflow 的第一个问题。我试图让 python 打开一个 .accdb 文件并运行一个已经在 Access 中定义的子例程。我设法使用此代码使用 Excel 来做到这一点:

import win32com.client
xl=win32com.client.Dispatch("Excel.Application")
xl.Visible=True
xl.Workbooks.Open(Filename="<mydirectory>\\open",ReadOnly=1)
xl.Application.Run("TestMe")
#...access spreadsheet data...
xl.Workbooks(1).Close(SaveChanges=0)
xl.Application.Quit()
xl=0

Sub TestMe 看起来像这样:

Sub TestMe()
MsgBox "Hi there"
End Sub

运行 Python 代码会立即启动 Excel,打开文件 open.xlsm 并显示一个消息框。到目前为止,一切都很好。感谢:Need skeleton code to call Excel VBA from PythonWin

我已经修改了代码以尝试通过 Access 实现相同的效果。我创建了一个名为“testdb”的新 .accdb 文件,并将上述子例程“TestMe”复制到 VBA 模块中。修改后的python代码如下:

import win32com.client
xl=win32com.client.Dispatch("Access.Application")
xl.Visible=True
xl.OpenCurrentDatabase("<mydirectory>\\testdb.accdb")
xl.Application.Run("TestMe")
#...access spreadsheet data...
xl.Workbooks(1).Close(SaveChanges=0)
xl.Application.Quit()
xl=0

主要变化是“Workbooks.Open”已更改为“OpenCurrentDatabase”。我首先试图找到更相似的东西,比如“Databases.Open”,但没有运气。运行新代码会启动 Access 并打开文件 testdb.accdb,但就是这样,没有消息框出现。我能想象到的唯一控制台输出是:

xl.Application.Run("TestMe")
File "<COMObject <unknown>>", line 14, in Run

result = self._oleobj_.InvokeTypes(*(dispid, LCID, wFlags, retType,        argTypes) + args)
pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2147352562), None)

我很茫然。任何帮助将不胜感激!

【问题讨论】:

  • 当您在 Access 会话中打开 testdb.accdb 并在那里测试您的程序时会发生什么?
  • 您是否尝试过完全限定过程名称?
  • HansUp:程序按预期运行,David Zemens:不,我真的不知道这意味着什么。将谷歌并尝试教育自己。谢谢你们的cmets!

标签: python ms-access vba win32com


【解决方案1】:

考虑使用调用模块中的函数的RunCode 操作创建一个新的 Access 宏对象。然后,使用 DoCmd.RunMacro 方法在 Python 的 Windows COM API 中调用宏。

Macro
RunCode: TestMe()

注意:只有函数可以被 RunCode 引用而不是子例程,除非您创建一个调用子例程的 VBA 模块函数:Call SubroutineName

Python

import win32com.client
ac = win32com.client.Dispatch("Access.Application")
ac.Visible=True
ac.OpenCurrentDatabase("<mydirectory>\\testdb.accdb")
ac.DoCmd.RunMacro('MacroName')

ac.DoCmd.CloseDatabase
ac = None

【讨论】:

  • 将 Sub 更改为 Function 并按照您的建议制作宏为我解决了问题。非常感谢!
  • @Parfait Python 代码只有在我有一个 ac.DoCmd 行时才能正常工作。就我而言,我想要两行ac.DoCmd,第一行将全部删除,第二个宏将加载新数据不起作用。只有删除宏有效(第一行),它打开访问给我一个消息框,数据已被删除,当我从删除宏中单击 msgbox 中的“确定”时,python 代码在假设运行更新宏时崩溃
  • @Greencolor 请提出一个新问题,并避免在此特定帖子中可能使未来读者感到困惑的这些旁白。
【解决方案2】:

我绝不是 Python 方面的专家,但对 Access 和 Excel VBA 相当熟悉。如果我在业余编程生涯中更早地了解 Python,我将永远不会尝试使用任何 VBA 代码来完成我下面打算做的事情。考虑到对 VBA 代码的时间投入,我必须找到一种方法......

在过去的几天/一周里,我一直在试图找到一种使用 Python 的方法->访问 VBA 并满足以下要求:

  • 从 Python 调用 Access VBA 函数
  • 从 Python 向 Access VBA 函数发送参数
  • 从 Access VBA 函数返回值到 Python

我使用 pythonnet 和 IronPython 尝试使用 clr,发现越来越多的混乱和不清楚的错误消息和异常。我尝试了上面建议的 DoCmd.RunMacro 方法,但 Access 宏不返回值。尝试了一个名为数据宏的新(ish)版本的 Access 宏(2010),它确实有一个名为 SetReturnVar 的操作,但除非通过更传统的宏并且如上所述,传统宏不返回值,否则它们不会与 VBA 对话。今天我更仔细地阅读了一些微软文档(Access Application.Run 方法):

Access Application.Run method

我并不完全了解“您不能从 Microsoft Access 以外的任何应用程序设置对单个 Microsoft Access 数据库的引用”这句话的含义,但我发现很多 Python Office 应用程序文章在谈论 PythonExcel VBA 时似乎更成功。我的理由是,因为我过去能够运行 Excel VBA Access VBA,如果 Python Excel VBA 和我读过的一样工作,那么一个解决方案(尽管很复杂)似乎是可能的(我认为正确的程序员称之为 hack)。

在剪切/粘贴代码 sn-ps 和调试大约 1 1/2 小时后:

Python

from win32com.client import Dispatch

FILELOC = r'C:\Users\Desktop\PyExcel.xlsm'
PROGNAME='Excel.Application'
num = 4

#open excel workbook containing VBA code
#...could do more to ensure excel isn't already running
xl = Dispatch(PROGNAME)
xl.Visible = True

#open excel file containing the VBA code
#...could do more to check if file is already open, etc 
xl.Workbooks.Open(Filename=FILELOC)
#call to VBA code within excel
rtrn_int = xl.Run("RunCOMObject", num)

#print return value
print(rtrn_int)

#Quit excel-this doesn't work very well and there are articles about
#Python or the COM object not being able to actually remove Excel
#from the task manager
xl.Quit()

Excel VBA

Option Explicit

Private Const ACCESS_FILELOC As String = "C:\Users\Desktop\Test.accdb"
Private Const TEMP_FILELOC As String = "C:\Users\Desktop\TestTemp.accdb"

Function RunCOMObject(intNum As Integer) As Integer

    Dim objAcc As Object, objProject As Object
    Dim accAppl As Access.Application
    Dim MyAppl As String

    MyAppl = "Access.Application"

    If Not IsRunning(MyAppl) Then  'Access not running, simply start 
          'up Access and open file
        Set accAppl = CreateObject(MyAppl) 'start Access
        accAppl.Visible = True
        accAppl.OpenCurrentDatabase (ACCESS_FILELOC) 'open file
    Else:  'Access is running
        On Error Resume Next
        Set accAppl = GetObject(, MyAppl)  'assign the running application 
            'to a variable
        On Error GoTo Err_File_Open 'use an error in attempting to rename 
            'the database of interest to determine if the open file is the 
            'desired file
        Name ACCESS_FILELOC As TEMP_FILELOC 'rename the file of interest
        Name TEMP_FILELOC As ACCESS_FILELOC 'file was successfully renamed 
            'therefore not open
        Call NoFileOrOther(accAppl, MyAppl)
    End If

Err_File_Open:
    'Required Access file is open
    RunCOMObject = accAppl.Run("TestLink", intNum) 'run the VBA function in 
       'Access
    accAppl.CloseCurrentDatabase  'close database
    accAppl.Quit 'quit Access
    Set accAppl = Nothing

End Function


Function IsRunning(ByVal MyAppl As String) As Boolean

    Dim applRef As Object

    On Error Resume Next 'error occurs if GetObject is unable to find a 
       'running version of the application

    Set applRef = GetObject(, MyAppl) 'attempt to obtain the required 
       'application object
    If Not applRef Is Nothing Then  'if application is already running
        Set applRef = Nothing
        IsRunning = True
    Else  'application is not running
        IsRunning = False
    End If
    Set applRef = Nothing
End Function

Sub NoFileOrOther(accAppl As Access.Application, MyAppl As String)

    On Error GoTo Err_No_FileOpen
    If accAppl.CurrentProject.Name <> "" Then  'Access active with another a 
       'different file open
        Set accAppl = CreateObject(MyAppl) 'start a new instance of Access
        accAppl.Visible = True
        accAppl.OpenCurrentDatabase (ACCESS_FILELOC) 'open file
    End If

    Exit Sub

Err_No_FileOpen:
    accAppl.OpenCurrentDatabase (ACCESS_FILELOC)  'in the event of Access 
       'being active without a database open

End Sub

访问 VBA

这是一个完全微不足道的示例,掩盖了我已经在 Access VBA 中编写的代码量来保证这种解决方法,但它用于演示方法

Option Compare Database
Option Explicit

Function TestLink(intNum As Integer) As Integer
    TestLink = intNum + 10
End Function

Python 输出:

14

成功!!!!该数字在 Python 中最初为 4,并作为参数发送到 Excel 和 Access,其中添加了 10,然后通过 Excel 返回到 Python print(rtrn_int) = 14。

如果有人知道如何在不使用 Excel VBA 作为中介的情况下从 Python -> 访问 VBA 并返回值给 Python 明确(即通过上面演示的类似严格)发送参数,我将很高兴收到您的来信。或者使用 pythonnet 引用 clr 的方法将同样受到赞赏。

【讨论】:

  • Thisthis 都在 C# 中工作,所以如果在 IronPython 中无法完成类似的事情,我会感到非常惊讶。
  • ...事实上,我刚刚确认 this 可以与 IronPython 2.7.7 一起使用。
  • 上述脚注,Excel VBA需要引用Microsoft Access Object库。
猜你喜欢
  • 1970-01-01
  • 2018-06-24
  • 1970-01-01
  • 2017-07-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
  • 2017-08-17
相关资源
最近更新 更多