return error怎么定义_专栏 | 函数重写: SAS 自定义函数的重写与修改...

3bff0166f405f0ad74721c9f87824187.png

在《SAS 技术内幕:从程序员到数据科学家》第五章函数封装中指出,在 SAS 中实现自定义函数功能可以有多种手段,主要包括:

  1.  DATA 步的 LINK-RETURN 语句进行扁平化伪函数封装,但调用仅限于同一 DATA 步内;

  2. PROC FCMP 编写用户自定义函数和 CALL 例程,在任何系统函数能被调用的地方都可调用;

  3. PROC PROTO 中用 C 语言编写自定义函数,或者链接外部动态链接库DLL文件,任何系统函数可调用处都可使用。

  4. PROC DS2 扩展自定义方法,但它仅用于 DS2 内部调用,DATA 步不能调用 DS2 中的函数。

  5. SAS 宏函数封装,可执行于 SAS 任何地方

  6. 其他特定PROC内可定义和调用的函数封装功能,如PROC COMPILE(Risk Dimension 模块)、PROC IML(矩阵运算模块),PROC CAS(SAS VIYA)等。

在传统上使用最方便的还是使用 PROC FCMP 和 PROC PROTO 来扩展系统功能,因为它们定义的函数和例程具有最广泛的适用性,系统函数能被调用的地方,它们就可以被调用。PROC PROTO 中所定义的 C 函数或链接的任何其他语言开发的动态库(DLL)中的函数,可以在 PROC FCMP 中部被调用和再封装,而 PROC FCMP 中的函数可以被 DATA步和 PROC DS2 所调用;但 PROC DS2 中所定义的函数是不能反过来被 DATA 步所调用的。

基于顺序的重写

本文今天探讨的问题是关于 PROC FCMP 函数重写机制问题。如果张三用 PROC FCMP 开发了一系列的功能,而李四对其中的某些函数实现并不满意,希望有一种机制能够替换掉原来的函数实现。也就是说需要类似于面向对象编程中子类可以 “覆盖” 从父类继承来的某个方法的机制。

函数重载(Overload)与函数重写(Override)是面向对象编程中的两个不同概念,前者是指函数名称相同,但函数参数的类型和个数不同,运行时能够自动根据实际参数的个数和类型正确调用对应函数的机制;而函数重写是指继承类(子类)对基类(父类)中的函数进行重新实现(在 C++ 中要求是基类的虚函数,Java中要求是基类中非final,非static 的方法),它要求函数签名——即函数名称、参数类型和顺序完全一样,但函数内部实现被改变。

考察如下代码,它定义了两个函数 a() 和 b(),代码编译存储在c:\temp 路径的 funcs.sas7bdat 文件中,即 mylib.funcs 数据集:

libname mylib "c:\temp";

proc fcmp outlib=mylib.funcs.v1;

  function a( );

    put "NOTE:Invokea() function in mylib library";

    return(1);

  endsub;

  function b();

    ret=a();

    put "NOTE:Invokeb() function in mylib library";

    return(ret+3);

  endsub;

run;

quit;

上面代码运行后就可以被别的 DATA 步调用,其中需要用到系统选项 cmplib= 来指定该数据集,而编译的 FCMP 函数就存储在该文件中:

options cmplib=(mylib.funcs);

data _null_;

  ret=b();

  put ret=;

run;

系统输出如下,所有被调用的函数 a() 和 b() 都是在 mylib 中所定义的:

NOTE: Invoke a() function in mylib library

NOTE: Invoke b() function in mylib library

ret=4

由于 mylib 逻辑库中的函数可能是别人实现的,我们一般不能直接去修改别人的实现,因此我们不能把函数直接输出到 mylib 中同一数据集中。更多情况是由于别人写的代码可能在别的地方被调用了,因此我们不应该去破坏别人代码的完整性,所以引用的逻辑库通常是一个只读逻辑库。此时我们如果想替换掉函数 a() 的实现,它只能输出到一个新的逻辑库中,比如 WORK 临时逻辑库。代码如下:

proc fcmp outlib=work.funcs.v2;

  function a( );

    put "NOTE:Invokea() function in work library";

    return(2);

  endsub;

run;

quit;

此时再次调用如下代码将会得到跟前面的例子一样的输出,在 WORK 中新定义的函数 a() 并没有被调用。

options cmplib=(mylib.funcs);

data _null_;

  ret=b();

  put ret=;

run;

要成功调用 WORK 逻辑库中新版本函数 a(),需要将 WORK 逻辑库中的数据集添加到系统选项 cmplib= 的尾部,才能让新版函数被优先搜索到,从而实现替换老版本函数的功能。代码如下:

options cmplib=(mylib.funcs work.funcs);

data_null_;

  ret=b();

  put ret=;

run;

系统输出如下,说明 WORK 中的函数 a() 被优先调用,结果 ret=5 也说明定义在 mylib中的b() 函数调用的子函数确实是定义在WORK 中的新版本函数a()。需要注意的是,如果名称相同的函数在多个库中都有定义,则系统选项 cmplib= 中越后指定的数据集中所定义的函数具有越高优先调用权,而不是越前面指定的函数具有越高的优先权

NOTE: Invoke a() function in work library

NOTE: Invoke b() function in mylib library

ret=5

这里还需要注意一个问题,尽管我们可以在PROC FCMP 中定义多个同名函数(它们会输出到同一个数据集中),但只有最后一个函数定义才真正起作用。因此可以说在单个输出的函数数据集中,SAS是无法实现函数重载定义的。

proc fcmp outlib=work.funcs.v3;

  function a(arg);

    put "NOTE:Invokea() function in work library with arg="arg;

    return(3);

  endsub;

run;

quit;

options cmplib=(work.funcs );

data _null_;

  ret=a();/*会引发错误*/

  put ret=;

run;

上面的代码将会报告如下错误,此时必须提供一个调用参数才能正确调用,比如 ret=a(2); 也就时说在相同的代码逻辑库中,同名函数以最后定义的为准。

ERROR 71-185: a 函数调用没有足够的参数。

基于拷贝的修改

由于SAS编译的函数结果是作为一个数据集存在的,我们是否可以拷贝一个包含函数定义的既有数据集,然后修改其中的部分函数定义呢?比如:

data work.funcs; /*试图克隆包含函数定义的数据集*/

  set mylib.funcs;

run;

proc fcmp outlib=work.funcs.v1;

  function a( );

    put "NOTE:Invokea() function in work library clone dataset";

    return(4);

  endsub;

run;

quit;

运行上面的 PROC FCMP 代码时系统会报告如下打开数据集错误,说明这种方法是无法实现克隆一个既有函数数据集并修改其中某个函数定义的。

ERROR: Cannot open data set work.funcs for write access because it is currently opened or

       already exists as astandard data set.

WARNING: The following functions will NOT be saved:

由于函数编译的输出结果只有数据集,而且该函数数据集显然可以在操作系统文件拷贝的方式在别的地方被引用。因此我们有理由怀疑用 DATA 步的 set 语句这种方式拷贝存在问题,也就是说这种拷贝数据集的方式只适用于标准的数据集,而不适用于函数定义的数据集。我们尝试用 PROC DATASETS 来拷贝包含函数定义的数据集:

proc datasets library=mylib nolist nowarn;

  copy out=work;

  select funcs;

run;

proc fcmp outlib=work.funcs.v2;

  function a( );

    put "NOTE:Invokea() function in work library clone dataset";

  return(5);

  endsub;

run;

quit;

随后我们尝试调用 WORK 逻辑库中的 b() 函数,发现它可以正常工作,而且调用结果也符合我们的预期:

options cmplib=(work.funcs);

data_null_;

  res= b();

  put res=;

run;

输出:

NOTE: Invoke a() function in work library clone dataset

NOTE: Invoke b() function in mylib library

res=8

需要注意在上面的例子中,我们只是调用了 WORK 逻辑库中的函数定义,并没有在 cmplib= 中指定原来的函数数据集 mylib.funcs,因此它是 ”先拷贝一份代码,然后修改它” (两者对比示意图如下)。也就是说,我们拷贝了 mylib 中的代码 funcs到 WORK 逻辑库中并修改 WORK逻辑库中的 a() 函数,原来只读逻辑库 mylib 中的所有函数 a() 和b() 并没有被篡改过,而是完整第保留了。

026777d1b589c1f7ecc64e5a79079252.png

基于搜索顺序的函数重写(左)与基于拷贝代码的函数修改

最后需要指出一点是,我们在定义新函数时可以通过 PROC FCMP的 INLIB指定需要引用和依赖的函数库,但编译结果并不会自动包含 INLIB= 数据集中定义的那些函数。因此我们在最终调用时依然需要使用 CMPLIB= 选项来指定包含所依赖子函数的那个函数数据集。考察如下代码:

proc fcmp outlib=work.funcs.v2  inlib=mylib.funcs;

  function a( );

    put "NOTE:Invokea() function in mylib library";

    return(1);

  endsub;

run;

quit;

options cmplib=(work.funcs );

data _null_;

  r=b();

run;

系统依然会报告如下错误,说函数 b() 未能找到,此时需要使用 options cmplib=( mylib.funcs  work.funcs);  才能修正它。

ERROR 68-185: 函数 B 未知,或无法访问。

后记:函数重写是十分常见的现实需求,比如当用户编写好神经网络梯度下降算法时,其中可能已经包含网络权重的默认初始化算法,比如用均匀分布的随机数对网络权重进行初始化。但在构造具体神经网络结构实例时,网络层数、节点数和连接方式是完全自定义的,而它的初始权重要根据特定需求进行初始化,然而误差反向传播算法可能是通用的。因此 SAS 编程者可以在 WORK 中重新定义 initweight 函数,但让原来的 mylib 中定义的通用算法调用用户新定义的自定义方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值