【问题标题】:VBA: Set variable to "Empty" or "Missing"? Handling multiple optional arguments?VBA:将变量设置为“空”或“缺失”?处理多个可选参数?
【发布时间】:2014-09-24 19:43:47
【问题描述】:

是否可以或需要将对象/数据设置为“空”或“缺失”变体? 我希望能够有条件地将可选参数传递给函数。有时我想使用可选参数,有时我不想。

在 Python 中,您可以通过使用 **kwdargs 将字典或列表解压缩到函数参数中轻松传递所需的任何可选参数。是否有类似的东西(或在 VBA 中破解它的方法),以便您可以传入 Empty/Missing 可选参数?

特别是,我正在尝试将 Application.Run 与任意数量的参数一起使用。

编辑

我基本上是在尝试这样做:

Public Function bob(Optional arg1 = 0, Optional arg2 = 0, Optional arg3 = 0, Optional arg4 = 0)
    bob = arg1 + arg2 + arg3 + arg4
End Function

Public Function joe(Optional arg1)
    joe = arg1 * 4
End Function


Public Sub RunArbitraryFunctions()
    'Run a giant list of arbitrary functions pseudocode

    Dim flist(1 To 500)
    flist(1) = "bob"
    flist(2) = "joe"
    flist(3) = "more arbitrary functions of arbitrary names"
    flist(N) = ".... and so on"

    Dim arglist1(1 To 4)        'arguments for bob
    Dim arglist2(1 To 1)        'arguments for joe 
    Dim arglist3(1 To M number of arguments for each ith function)


    For i = 1 To N
        'Execute Application.Run,
        'making sure the right number of arguments are passed in somehow.
        'It'd also be nice if there was a way to automatically unpack arglisti
        Application.Run flist(i) arglisti(1), arglisti(2), arglisti(3), ....
    Next i

End Sub

由于每个函数调用的参数数量都会发生变化,因此确保将正确数量的输入输入到 Application.Run 中的可接受方法是什么?

等效的 Python 代码是

funclist = ['bob', 'joe', 'etc']
arglists = [[1,2,3],[1,2],[1,2,3,4,5], etc]

for args, funcs in zip(arglists, funclist):
    func1 = eval(funcs)
    output = func1(*args)

【问题讨论】:

  • VBA 中有一个名为 IsMissing() 的函数,用于评估可选参数 msdn.microsoft.com/en-us/library/office/…
  • 我不完全确定这就是你要找的东西,但你检查过ParamArray吗?为Variant类型,可以判断是否为空,取任意个元素。
  • 除了 ParamArray 还可以使用 Optional 关键字,此外,还可以选择设置一个默认值;根据变量的类型,您可以通过 IsMissing 进行测试;没什么; IsEmpty(如果您选择将 Variant 设置为空)等。
  • @Ron 谢谢你的评论正是我所需要的。我不知道你可以这样做:b = Empty

标签: excel vba excel-2010


【解决方案1】:

VBA 中,您使用 ParamArray 将选项输入输入到函数中。

Pearson Material

【讨论】:

  • +1。 ParamArray 最接近Pythonkwargs(虽然它不是字典)。
【解决方案2】:

例程可以通过两种方式更改必须提供给它的参数数量:

一个例程可以使用其中一个或两个。


Optional 参数可能具有严格类型(例如Optional s As String),但随后将无法检测它是否被传递。如果您不为此类参数传递值,则将使用正确的“空白”风格,这与手动传递该空白值没有区别。
因此,拥有Public Sub Bob(Optional S As String),您无法从Bob 内部检测到它是被称为Bob 还是Bob vbNullString
一个可选参数可能有一个默认值,它会遇到同样的问题。因此,拥有Public Sub Bob(Optional S As String = "Default Value"),您无法检测到Bob 是被称为Bob 还是Bob "Default Value"

为了能够真正检测是否传递了可选参数,它们必须输入为Variant。然后可以在例程中使用特殊函数IsMissing 来检测是否传递了参数。

Public Sub Bob(Optional a, Optional b, Optional c, Optional d)
    Debug.Print IsMissing(a), IsMissing(b), IsMissing(c), IsMissing(d)
End Sub
Bob 1, , 3  ' Prints False, True, False, True

ParamArray 只能是最后一个参数,并且它允许从这个位置开始传递无限*数量的参数。所有这些参数都打包在一个 Variant 数组中(此处没有静态类型选项)。

IsMissing 函数不适用于 ParamArray 参数(始终返回 False)。知道传递了多少参数的方法是将UBound(args)LBound(args) 进行比较。请注意,这仅告诉您使用了多少参数“槽”,但其中一些实际上可能会丢失!

Public Sub BobArray(ParamArray a())
    Dim i As Long
    For i = LBound(a) To UBound(a)
        Debug.Print IsMissing(a(i)), ;
    Next
    Debug.Print
End Sub
BobArray                 ' Prints empty line (the For loop is not entered due to UBound < LBound)
Sheet1.BobArray 1, 2, 3  ' Prints False, False, False
Sheet1.BobArray 1, , 3   ' Prints False, True, False

请注意,您不能为 ParamArray 的尾随参数传递“缺失”值,即这是非法的:

Sheet1.BobArray 1, , 3,   ' Does not compile

但是,您可以使用下面描述的技巧来解决这个问题。


您在问题中涉及的一个有趣用例是提前准备所有参数的数组,将其传递给函数,填充所有参数“占位符”,但仍希望函数检测到某些参数是缺失(未通过)。

通常这是不可能的,因为如果传递了任何内容(即使是“空白”值,例如 vbNullStringEmptyNullNothing),那么它仍然算作通过,并且 @987654354 @ 将返回 False

幸运的是,特殊的Missing 值只不过是一个特殊构造的Variant,即使不知道如何手动构造该值,我们也可以欺骗编译器将其泄露:

Public Function GetMissingValue(Optional ByVal IgnoreMe As Variant) As Variant
    If IsMissing(IgnoreMe) Then
        GetMissingValue = IgnoreMe
    Else
        Err.Raise 5, , "I told you to ignore me, didn't I"
    End If
End Function
Dim missing As Variant
missing = GetMissingValue()

Dim arglist1(1 To 4) As Variant
arglist1(1) = 42
arglist1(2) = missing
arglist1(3) = missing
arglist1(4) = "!"

Bob arglist1(1), arglist1(2), arglist1(3), arglist1(4)   ' Prints False, True, True, False

现在,我们可以解决无法将“缺失”传递到 ParamArray 的尾随“槽”的问题:

Dim arglist1(1 To 4) As Variant
arglist1(1) = 42
arglist1(2) = missing
arglist1(3) = missing
arglist1(4) = missing

BobArray arglist1(1), arglist1(2), arglist1(3), arglist1(4) ' Prints False, True, True, True

但请注意,此解决方法仅在您直接调用 BobArray 时才有效。如果您使用Application.Run,它将不起作用,因为Run 方法将在将任何尾随“缺失”参数传递给被调用例程之前丢弃它们:

Dim arglist1(1 To 4) As Variant
arglist1(1) = 42
arglist1(2) = missing
arglist1(3) = missing
arglist1(4) = missing

Application.Run "BobArray", arglist1(1), arglist1(2), arglist1(3), arglist1(4)
' Prints False, because only one argument is passed

【讨论】:

    【解决方案3】:

    除了@GSerg 的非常全面的答案(我没有足够的声誉来发表评论),分配给 Missing 参数的“特殊”值具有作为错误值的“外观” - 它转换为“错误 448 "(未找到命名参数)使用CStr(),并以TRUE 响应IsError()。但是,尝试在传递给过程之前使用CvErr(448) 预设参数(希望它会被识别为缺失)失败,可能是因为该值在某种程度上与错误值“不完全”相同。

    @GSerg 建议了一种方法,在缺少参数时“记录”编译器实际传递的值,并在传递给需要被愚弄的过程之前使用该方法预设一个虚拟参数。这种方法确实有效,我只是扩展了@GSerg 的函数,以通过不带参数的递归调用替换他的错误消息(如果它无意中用参数调用),以确保成功的结果。用法只是在传递给过程之前预设虚拟变量(然后将其/它们视为缺失):Dummy_Var = Missing()

    Public Function Missing(Optional ByVal X As Variant) As Variant
        
        If IsMissing(X) Then    'correctly called
            Missing = X
        Else                    'bad user call
            Missing = Missing() 'recursive call (no arg!)
        End If
    
    End Function
    

    我刚刚对Application.Run 进行了快速试用。早期嵌入的“缺失”参数(即,后跟“正常”参数)似乎在被调用过程中成功注册为“缺失”。然而,最后的尾随“缺失”参数也是如此——无论是实际由Run 方法传递,还是被截断(如@GSerg 所建议),但仍由编译器填写为真正缺失。

    有趣且有用(对于利基市场),编译器似乎可以容忍额外的“缺失”参数(超出过程定义的参数)不会生成“错误数量的参数”与额外的“正常”参数相关联的消息。这开辟了使用Application.Run(当需要可变数量的参数时)的过程调用的可能性,该调用由单个通用调用(必要时最多可使用30个参数)实现,并用假的“缺失”参数填充,而不是必须提供了几个不同长度和/或参数配置的替代调用来处理精确的过程定义。

    【讨论】:

      【解决方案4】:

      因此,解决可选使用参数的问题看起来像我在Calling vba macro from python with unknown number of arguments 中的问题,请相应地检查一下。

      因此: 使用 Python

      def run_vba_macro(str_path, str_modulename, str_macroname, **kwargs):
          if os.path.exists(str_path):
              xl=win32com.client.DispatchEx("Excel.Application")
              wb=xl.Workbooks.Open(str_path, ReadOnly=0)
              xl.Visible = True
              if kwargs:
                      params_for_excel = list(kwargs.values())
                      xl.Application.Run(os.path.basename(str_path)+"!"+str_modulename+'.'+str_macroname,
                                                *params_for_excel,)
              else:
                      xl.Application.Run(os.path.basename(str_path)
                                                +"!"+str_modulename
                                                +'.'+str_macroname)
              wb.Close(SaveChanges=0)
              xl.Application.Quit()
              del xl
      
      #example
      kwargs={'str_file':r'blablab'}
      run_vba_macro(r'D:\arch_v14.xlsm',
                    str_modulename="Module1",
                    str_macroname='macro1',
                    **kwargs)
      #other example
      kwargs={'arg1':1,'arg2':2}
      run_vba_macro(r'D:\arch_v14.xlsm',
                    str_modulename="Module1",
                    str_macroname='macro_other',
                    **kwargs)
      

      使用 VBA

      Sub macro1(ParamArray args() as Variant)
          MsgBox("success the str_file argument was passed as =" & args(0))
      End Sub
      
      Sub macro_other(ParamArray args() as Variant)
          MsgBox("success the arguments have passed as =" & str(args(0)) & " and " & str(args(1)))
      End Sub
      


      另一个仅使用 VBA 的用例也可供参考。这是一个尚未得到回答并且存在很长时间的问题,尽管最近它由社区服务器自动更新,并相应地提供了一些与相关链接的好主意。

      如果你使用这个,这是一个你可以做到的答案:

      Sub pass_one()
          Call flexible("a")
      End Sub
      Sub pass_other()
          Call flexible("a", 2)
      End Sub
      Sub flexible(ParamArray args() As Variant)
          Dim i As Long
          MsgBox ("I have received " & _
                  Str(UBound(args) + 1) & _
                  " parameters.")
          For i = 0 To UBound(args)
              MsgBox (TypeName(args(i)))
          Next i
      End Sub
      

      仅适用于同时使用 Python 的开发人员:
      如果您使用 Python 的 kwargs,只需简单地使用 starr 表达式并传递一个 Python 元组。 在这里(它与我在Calling vba macro from python with unknown number of arguments 中的问题有关)
      干杯。

      【讨论】:

      • that has not been answered - 讨论完全相同的事情的answer from 2014 怎么样(据我所知,这不是 OP 想要的)?
      • @GSerg 好的,谢谢,已经对其进行了编辑,现在希望它有所改进,只要一点点。干杯。
      • OP 没有提到从 python 中使用 Excel,他们只提到 python 作为调用具有可变数量参数的例程时发生的情况的一个例子,否则它们在 VBA 中工作。我也不认为有必要发布任何人的问题作为答案。
      • 嗯,用户询问“我希望能够有条件地将可选参数传递给函数。有时我想使用可选参数,有时我不想。”他对 python 很熟悉,所以我给出了一个与 Python 一起使用它的工作示例,另一个仅与 VBA 一起使用。这也不是问题,因为代码已经发生了一些变化,所以它正在工作。同样,如果这不添加我可以要求删除的信息也没关系,尽管在我看来它添加了一个完整的工作解决方案。无论哪种方式都可以,干杯。
      猜你喜欢
      • 2017-09-17
      • 2019-06-02
      • 1970-01-01
      • 1970-01-01
      • 2019-09-28
      • 1970-01-01
      • 1970-01-01
      • 2015-02-19
      • 2014-02-04
      相关资源
      最近更新 更多