标签:
UDF全称User Defined Function,即用户自定义函数。ODPS提供了很多内建函数来满足用户的计算需求,同时用户还可以通过创建自定义函数来满足不同的计算需求。UDF在使用上与普通的 SQL内建函数 类似。
在ODPS中,用户可以扩展的UDF有三种,分别是:
UDF 分类 | 描述
User Defined Scalar Function 通常也称之为UDF
自定义函数,准确的说是用户自定义标量函数 (User Defined Scalar Function)。UDF的输入与输 出是一对一的关系,即读入一行数据, 写出一条输出值。UDAF(User Defined Aggregation Function)
自定义聚合函数,其输入与输出是多对一的关系, 即将多条输入记录聚合成一条输出值。可以与 SQL中的Group By语句联用。具体语法请参考聚合函数 。UDTF(User Defined Table Valued Function)
自定义表函数,是用来解决一次函数调用输出 多行数据场景的,也是唯一能返回多个字段的自定 义函数。而UDF及UDAF只能一次计算输出一条 返回值。
注解
UDF广义的说法代表了自定义标量函数,自定义聚合函数及自定义表函数三种类型的自定义函数的集合。狭义来说,仅代表用户自定义标量函数。文档会经常使用这一名词,请读者根据文档上下文判断具体含义。
ODPS UDF的Python版本为2.7,并以沙箱模式执行用户代码,即代码是在一个受限的运行环境中执行的,在这个环境中,被禁止的行为包括:
基于上述原因,用户上传的代码必须都是纯Python实现,C扩展模块是被禁止的。
此外,Python的标准库中也不是所有模块都可用,涉及到上述功能的模块都会被禁止。具体标准库可用模块说明如下:
|
运行环境中还安装了除标准库以外比较常用的三方库,做为标准库的补充。支持的三方库列表如下:
警告
三方库的使用同样受到禁止本地、网络IO或其他在受限环境下的限制,因此三方库中涉及到相关功能的API也是被禁止的。
@odps.udf.annotate(signature)
Python UDF目前支持ODPS SQL数据类型有:bigint, string, double, boolean和datetime。SQL语句在执行之前,所有函数的参数类型和返回值类型必须确定。因此对于Python这一动态类型语言,需要通过对UDF类加decorator的方式指定函数签名。
函数签名signature通过字符串指定,语法如下:
arg_type_list ‘->‘ type_list arg_type_list: type_list | ‘*‘ | ‘‘ type_list: [type_list ‘,‘] type type: ‘bigint‘ | ‘string‘ | ‘double‘ | ‘boolean‘ | ‘datetime‘ |
下面是合法的signature的例子:
‘bigint,double->string‘ # 参数为bigint、double,返回值为string ‘bigint,boolean->string,datetime‘# UDTF参数为bigint、boolean,返回值为string,datetime ‘*->string‘ # 变长参数,输入参数任意 ,返回值为string ‘->double‘ # 参数为空,返回值为double |
Query语义解析阶段会将检查到不符合函数签名的用法,抛出错误禁止执行。执行期,UDF函数的参数会以函数签名指定的类型传给用户。用户的返回值类型也要与函数签名指定的类型一致,否则检查到类型不匹配时也会报错。ODPS SQL数据类型对应Python类型如下:
注解
|
odps.udf.int(value[, silent=True])
Python builtin函数 int 的修改。增加了参数 silent 。当 silent 为 True 时,如果 value 无法转为 int ,不会抛出异常,而是返回 None 。
实现Python UDF非常简单,只需要定义一个new-style class,并实现 evaluate 方法。下面是一个例子:
from odps.udf import annotate @annotate("bigint,bigint->bigint") class MyPlus(object): def evaluate(self, arg0, arg1): if None in (arg0, arg1): return None return arg0 + arg1 |
注解:Python UDF必须通过annotate指定函数签名
继承此类实现Python UDAF。
实现此方法返回聚合函数的中间值的buffer。buffer必须是mutable object(比如list, dict),并且buffer的大小不应该随数据量递增,在极限情况下,buffer marshal过后的大小不应该超过2Mb。
实现此方法将args聚合到中间值buffer中。
实现此方法将两个中间值buffer聚合到一起,即将pbuffer merge到buffer中。
实现此方法将中间值buffer转换为ODPS SQL基本类型。
下面是一个UDAF求平均值的例子。
#coding:utf-8 from odps.udf import annotate from odps.udf import BaseUDAF @annotate(‘double->double‘) class Average(BaseUDAF): def new_buffer(self): return [0, 0] def iterate(self, buffer, number): if number is not None: buffer[0] += number buffer[1] += 1 def merge(self, buffer, pbuffer): buffer[0] += pbuffer[0] buffer[1] += pbuffer[1] def terminate(self, buffer): if buffer[1] == 0: return 0 return buffer[0] / buffer[1] |
Python UDTF的基类,用户继承此类,并实现 process, close 等方法。
初始化方法,继承类如果实现这个方法,则必须在一开始调用基类的初始化方法 super(BaseUDTF,self).__init__() 。
__init__ 方法在整个UDTF生命周期中只会被调用一次,即在处理第一条记录之前。当UDTF需要保存内部状态时,可以在这个方法中初始化所有状态。
这个方法由ODPS SQL框架调用,SQL中每一条记录都会对应调用一次 process , process 的参数为SQL语句中指定的UDTF输入参数。
UDTF的输出方法,此方法由用户代码调用。每调用一次 forward ,就会输出一条记录。 forward 的参数为SQL语句中指定的UDTF的输出参数。
UDTF的结束方法,此方法由ODPS SQL框架调用,并且只会被调用一次,即在处理完最后一条记录之后。
下面是一个UDTF的例子。
#coding:utf-8 # explode.py from odps.udf import annotate from odps.udf import BaseUDTF @annotate(‘string -> string‘) class Explode(BaseUDTF): """将string按逗号分隔输出成多条记录""" def process(self, arg): props = arg.split(‘,‘) for p in props: self.forward(p) |
注解
Python UDTF也可以不加annotate指定参数类型和返回值类型。这样,函数在SQL中使用时可以匹配任意输入参数,但返回值类型无法推导,所有输出参数都将认为是string类型。因此在调用 forward 时,就必须将所有输出值转成 str 类型。
Python UDF可以通过 odps.distcache 模块引用资源文件,目前支持引用文件资源和表资源。
release-2012.09.03 新版功能.
返回指定名字的资源内容。 resource_name 为 str 类型,对应当前Project中已存在的资源名。如果资源名非法或者没有相应的资源,会抛出异常。
返回值为 file-like object ,在使用完这个object后,调用者有义务调用 close 方法释放打开的资源文件。
下面是使用 get_cache_file 的例子:
from odps.udf import annotate from odps.distcache import get_cache_file @annotate(‘bigint->string‘) class DistCacheExample(object): def __init__(self): cache_file = get_cache_file(‘test_distcache.txt‘) kv = {} for line in cache_file: line = line.strip() if not line: continue k, v = line.split() kv[int(k)] = v cache_file.close() self.kv = kv def evaluate(self, arg): return self.kv.get(arg) |
odps.distcache.get_cache_table(resource_name)
release-2012.11.14 新版功能.
返回指定资源表的内容。 resource_name 为 str 类型,对应当前Project中已存在的资源表名。如果资源名非法或者没有相应的资源,会抛出异常。
返回值为 generator 类型,调用者通过遍历获取表的内容,每次遍历得到的是以 tuple 形式存在的表中的一条记录。
下面是使用 get_cache_table 的例子:
from odps.udf import annotate from odps.distcache import get_cache_table @annotate(‘->string‘) class DistCacheTableExample(object): def __init__(self): self.records = list(get_cache_table(‘udf_test‘)) self.counter = 0 self.ln = len(self.records) def evaluate(self): if self.counter > self.ln - 1: return None ret = self.records[self.counter] self.counter += 1 return str(ret) |
当一个Query中有多个相同UDF,并且他们的参数也都一致时,这些UDF在执行时会被优化成只执行一次。例如:
random.seed(12345) @annotate(‘bigint->bigint‘) class MyRand(object): def evaluate(self, a): return random.randint(0, 10)
实现一个Rand函数,希望每次调用Rand时返回一个随机值。
> select MyRand(c_int_a), MyRand(c_int_a) from udf_test; +------------+------------+ | _c0 | _c1 | +------------+------------+ | 4 | 4 | | 0 | 0 | | 9 | 9 | | 3 | 3 | +------------+------------+
可以看到默认情况下,同一行的两次Rand调用返回值结果一样,这是因为被优化后只执行一次导致的。如果不想要这个优化,可以通过设置配置项odps.sql.udf.optimize.reuse 取消这个优化:
> set odps.sql.udf.optimize.reuse=false; > select MyRand(c_int_a), MyRand(c_int_a) from udf_test; +------------+------------+ | _c0 | _c1 | +------------+------------+ | 4 | 0 | | 9 | 3 | | 4 | 2 | | 6 | 1 | +------------+------------+
ODPS为Python提供的类有
@odps.udf.annotate(signature),ODPS SQL数据类型对应Python类型如下:
odps.udf.int(value[, silent=True])
# 定义一个new-style class,并实现 evaluate 方法
from odps.udf import annotate @annotate("bigint,bigint->bigint") class MyPlus(object): def evaluate(self, arg0, arg1): if None in (arg0, arg1): return None return arg0 + arg1 |
class odps.udf.BaseUDAF—继承此类实现Python UDAF。
BaseUDAF类拥有的四个方法如下:
下面是一个UDAF求平均值的例子。
#coding:utf-8 from odps.udf import annotate from odps.udf import BaseUDAF @annotate(‘double->double‘) class Average(BaseUDAF): def new_buffer(self): return [0, 0] def iterate(self, buffer, number): if number is not None: buffer[0] += number buffer[1] += 1 def merge(self, buffer, pbuffer): buffer[0] += pbuffer[0] buffer[1] += pbuffer[1] def terminate(self, buffer): if buffer[1] == 0: return 0 return buffer[0] / buffer[1] |
class odps.udf.BaseUDTF—Python UDTF的基类,用户继承此类,并实现 process , close 等方法。
BaseUDTF类拥有的四个方法
#coding:utf-8 # explode.py from odps.udf import annotate from odps.udf import BaseUDTF @annotate(‘string -> string‘) class Explode(BaseUDTF): """将string按逗号分隔输出成多条记录 """ def process(self, arg): props = arg.split(‘,‘) for p in props: self.forward(p) |
标签:
原文地址:http://www.cnblogs.com/Bfrican/p/4478227.html