UDF development (Python 3)

Updated at:

You can use MaxCompute to develop user-defined functions (UDFs) in Python 3 for specific business logic. This topic explains how to write a Python 3 UDF.

UDF code structure

You can use MaxCompute Studio to write a UDF in Python 3. The code must contain the following components:

  • Module import: Required.

    At a minimum, you must include from odps.udf import annotate to import the function signature decorator. This lets MaxCompute recognize the function signature you define. If your UDF code needs to reference a file resource or table resource, you must also include from odps.distcache import get_cache_file or from odps.distcache import get_cache_table, respectively.

  • Function signature: Required.

    The format is @annotate(<signature>), where signature defines the data types of the function's input parameters and return value. For more information about function signatures, see Function signatures and data types.

  • Custom Python class: Required.

    The class organizes your UDF code and defines the variables and methods that implement your business logic. You can also reference a built-in third-party library or reference file and table resources in your code. For more information, see Third-party library or Reference resources.

  • The evaluate method: Required.

    This method is part of the custom Python class. The evaluate method defines the input parameters and the return value. A Python class can contain only one evaluate method.

The following code provides a UDF example.

# Import the function signature module.
from odps.udf import annotate
# Define the function signature.
@annotate("bigint,bigint->bigint")
# Define the custom Python class.
class MyPlus(object):
# Implement the evaluate method.
    def evaluate(self, arg0, arg1):
        if None in (arg0, arg1):
            return None
        return arg0 + arg1

Hard limits

Internet access — UDFs cannot access the Internet by default. To enable Internet access, submit a network connection application. After approval, the MaxCompute technical support team will contact you to establish the connection. For details, see Network Connection Request FormNetwork connection process.

VPC access — UDFs cannot access resources in a virtual private cloud (VPC) by default. To enable VPC access, establish a network connection between MaxCompute and the VPC. For details, see Use UDFs to access resources in VPCs.

Unsupported table types — UDFs, UDAFs, and UDTFs cannot read data from the following table types:

  • Tables on which schema evolution has been performed

  • Tables that contain complex data types

  • Tables that contain JSON data types

  • Transactional tables

Usage notes

Python 3 is not compatible with Python 2. Before using Python 3, consider its compatibility issues. You cannot use both Python 3 and Python 2 in the same SQL statement.

Note

Python 2 reached its end of life (EOL) in early 2020. We recommend that you migrate your projects based on their type.

UDF development: General workflow

Developing a UDF involves preparing the environment, writing the code, uploading and registering it, and then calling and debugging the function. MaxCompute supports various development tools. The following sections use MaxCompute Studio, DataWorks, and odpscmd as examples to describe this workflow.

MaxCompute Studio

  1. Prerequisites

    Before you develop and debug a UDF in MaxCompute Studio, you must install MaxCompute Studio and connect it to a MaxCompute project. For more information, see the following topics:

    1. Install MaxCompute Studio

    2. Create a MaxCompute project connection

    3. Configure a Python development environment

  2. Write UDF code.

    1. In the Project panel, under the MaxCompute Studio directory, right-click scripts and select New > MaxCompute Python.

    2. In the Create new MaxCompute python class dialog box, enter a class name for Name, select Python UDF as the type, and then click OK.

    3. Write the UDF code in the editor.

      from odps.udf import annotate
      
      @annotate("string,bigint->string")
      class GetUrlChar(object):
      
          def evaluate(self, url, n):
              if n == 0:
                  return ""
              try:
                  index = url.find(".htm")
                  if index < 0:
                      return ""
                  a = url[:index]
                  index = a.rfind("/")
                  b = a[index + 1:]
                  c = b.split("-")
                  if len(c) < n:
                      return ""
                  return c[-n]
              except Exception:
                  return "Internal error"
                  
      Note

      For information about how to debug Python UDFs locally, see Test a UDF.

  3. Upload and register the UDF.

    Right-click the target Python program and select Deploy to server…. Configure the function name and click OK. For more information, see Upload a file and register a function.

    In this example, the function name is set to UDF_GET_URL_CHAR.

  4. Call the UDF.

    In the left-side navigation pane, click Project Explore. Right-click the target MaxCompute project, select Open Console, then enter and run the SQL statement to call the UDF.

    SET odps.sql.python.version=cp37; -- This command is required to enable Python 3 for the UDF.
    SELECT UDF_GET_URL_CHAR("http://www.taobao.com/a.htm", 1);

    The following result is returned:

    +-----+
    | _c0 |
    +-----+
    |  a  |
    +-----+

DataWorks

  1. Prerequisites

    Before you develop and debug a UDF in DataWorks, you must activate DataWorks and associate it with a MaxCompute project. For more information, see Connect to MaxCompute by using DataWorks.

  2. Write UDF code.

    You can develop the UDF code in any Python development tool and package it. The following code is an example.

    from odps.udf import annotate
    
    @annotate("string,bigint->string")
    class GetUrlChar(object):
    
        def evaluate(self, url, n):
            if n == 0:
                return ""
            try:
                index = url.find(".htm")
                if index < 0:
                    return ""
                a = url[:index]
                index = a.rfind("/")
                b = a[index + 1:]
                c = b.split("-")
                if len(c) < n:
                    return ""
                return c[-n]
            except Exception:
                return "Internal error"
                
  3. Upload and register the UDF.

    You can use DataWorks to upload the packaged code and register the UDF. For more information, see the following topics:

    1. Create and use MaxCompute resources

    2. Create and use a user-defined function

  4. Call the UDF.

    After you register the UDF, you can create an ODPS SQL node, and write and run an SQL statement in the node to call and debug the UDF. For more information about how to create an ODPS SQL node, see Develop an ODPS SQL task. The following code provides an example of the SQL statement.

    SET odps.sql.python.version=cp37; -- This command is required to enable Python 3 for the UDF.
    SELECT UDF_GET_URL_CHAR("http://www.taobao.com/a.htm", 1);

odpscmd

  1. Prerequisites

    Before you use odpscmd to develop and debug a UDF, you must download and install odpscmd and configure the config file to connect to a MaxCompute project. For more information, see Connect by using the MaxCompute client (odpscmd).

  2. Write UDF code.

    You can develop the UDF code in any Python development tool and package it. The following code is an example.

    from odps.udf import annotate
    
    @annotate("string,bigint->string")
    class GetUrlChar(object):
    
        def evaluate(self, url, n):
            if n == 0:
                return ""
            try:
                index = url.find(".htm")
                if index < 0:
                    return ""
                a = url[:index]
                index = a.rfind("/")
                b = a[index + 1:]
                c = b.split("-")
                if len(c) < n:
                    return ""
                return c[-n]
            except Exception:
                return "Internal error"
                
  3. Upload and register the UDF.

    You can use odpscmd to upload the packaged code and register the UDF. For more information, see the following topics:

    1. ADD PY

    2. CREATE FUNCTION

  4. Call the UDF.

    After you register the UDF, you can write and run an SQL statement to call and debug the UDF. The following code provides an example of the SQL statement.

    SET odps.sql.python.version=cp37; -- This command is required to enable Python 3 for the UDF.
    SELECT UDF_GET_URL_CHAR("http://www.taobao.com/a.htm", 1);

Install the NumPy library

The built-in Python 3 runtime environment in MaxCompute does not include the NumPy third-party library. If your UDF requires NumPy, you must manually upload the NumPy WHEEL package. When you download the NumPy package from PyPI or a mirror site, the file name is in the format numpy-<version>-cp37-cp37m-manylinux1_x86_64.whl. For more information about how to upload a package, see Resource operations or Use a third-party package in a Python UDF.

For a list of standard libraries that Python 3 supports, see Python 3 standard library.

Function signatures and data types

The format of a function signature is as follows:

@annotate(<signature>)

The signature is a string that specifies the data types of the input parameters and return value. When you execute the UDF, the data types of the input parameters and return value must be the same as those specified in the function signature. During semantic parsing, the system checks for usage that does not conform to the function signature. If a type mismatch is detected, the system reports an error.

'arg_type_list -> type'

Where:

  • arg_type_list: Specifies the data types of the input parameters. You can specify multiple input parameters, separated by commas (,). The supported data types are BIGINT, STRING, DOUBLE, BOOLEAN, DATETIME, DECIMAL, FLOAT, BINARY, DATE, DECIMAL(precision,scale), CHAR, VARCHAR, and complex data types such as ARRAY, MAP, and STRUCT, including nested complex data types.

    arg_type_list also supports an asterisk (*) or an empty string ('').

    • If you set arg_type_list to an asterisk (*), the function accepts any number of input parameters.

    • If you set arg_type_list to an empty string (''), the function has no input parameters.

  • type: Specifies the data type of the return value. A UDF returns a single column. The supported data types are: BIGINT, STRING, DOUBLE, BOOLEAN, DATETIME, DECIMAL, FLOAT, BINARY, DATE, DECIMAL(precision,scale), and complex data types such as ARRAY, MAP, and STRUCT, including nested complex data types.

Note

When you write UDF code, you can select appropriate data types based on the data type edition of your MaxCompute project. For more information about data type editions and the data types that each edition supports, see Data type editions.

The following table provides examples of valid function signatures.

Signature example

Description

'bigint,double->string'

The input parameter types are BIGINT and DOUBLE, and the return value type is STRING.

'*->string'

The function accepts any number of input parameters, and the return value type is STRING.

'->double'

The function has no input parameters, and the return value type is DOUBLE.

'array<bigint>->struct<x:string, y:int>'

The input parameter type is ARRAY<BIGINT>, and the return value type is STRUCT<x:STRING, y:INT>.

'->map<bigint, string>'

The function has no input parameters, and the return value type is MAP<BIGINT, STRING>.

To ensure data type compatibility between your Python UDF and MaxCompute, you must understand their data type mappings.

MaxCompute SQL type

Python 3 type

BIGINT

INT

STRING

UNICODE

DOUBLE

FLOAT

BOOLEAN

BOOL

DATETIME

DATETIME.DATETIME

FLOAT

FLOAT

CHAR

UNICODE

VARCHAR

UNICODE

BINARY

BYTES

DATE

DATETIME.DATE

DECIMAL

DECIMAL.DECIMAL

ARRAY

LIST

MAP

DICT

STRUCT

COLLECTIONS.NAMEDTUPLE

Referencing resources

A Python UDF can reference resources using the odps.distcache module. It supports referencing file resources and table resources.

  • odps.distcache.get_cache_file(resource_name, mode): Returns the content of a specified file resource in the specified mode.

    • resource_name must be the name of an existing table resource in your MaxCompute project. If the name is invalid or the table does not exist, an error is returned.

    • The mode parameter is a STRING. The default value is 't'. If you set mode to 't', the file is opened in text mode. If you set mode to 'b', the file is opened in binary mode.

    • The return value is a file-like object. After using the object, you must call the close method to release the file handle.

    The following code provides an example of how to reference a file resource.

    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): Returns the content of a specified table resource.

    • The resource_name parameter corresponds to the name of an existing table resource in the current MaxCompute project. An exception is thrown if the table resource name is invalid or the resource does not exist. You can read data of the following types from the table: BIGINT, STRING, DOUBLE, BOOLEAN, DATETIME, FLOAT, CHAR, VARCHAR, BINARY, DATE, DECIMAL, ARRAY, MAP, and STRUCT.

    • The return value is of the Generator type. The caller traverses the table to obtain its content. Each iteration retrieves a record from the table in the form of an array.

The following code provides an example of how to reference a table resource.

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)

Calling UDFs

After developing a Python 3 UDF by following the development workflow, you can call it in MaxCompute SQL as follows:

Enable Python 3

By default, MaxCompute uses Python 2. To use Python 3, set a session flag to enable it and commit this setting with your SQL statement.

set odps.sql.python.version=cp37;

Call the function

  • Use the UDF in its owner MaxCompute project: The usage is similar to that of a built-in function. You can use the UDF in the same way you use a built-in function.

  • Use the UDF across projects: You can use a UDF from one project (for example, Project B) in another (Project A). The following sample code shows an example of cross-project syntax: select B:udf_in_other_project(arg0, arg1) as res from table_t;. For more information about cross-project sharing, see Access resources across projects by using packages.

Migrate Python 2 UDFs

Python 2 reached its EOL in early 2020. We recommend migrating your projects based on their type:

  • New projects: For new MaxCompute projects or projects where you are writing Python UDFs for the first time. We recommend that you use Python 3 to write all Python UDFs.

  • Existing projects: For projects with many existing Python 2 UDFs, exercise caution when enabling Python 3. If you plan to gradually migrate all Python 2 UDFs to Python 3, we recommend that you use the following methods:

    • New jobs and new UDFs: Use Python 3 to write UDFs and enable Python 3 at the session level. For more information about how to enable Python 3, see Enable Python 3.

    • Python 2 UDFs: Rewrite Python 2 UDFs to make them compatible with both Python 2 and Python 3. For more information about how to rewrite the UDFs, see Porting Python 2 Code to Python 3.

      Note

      If you need to write public UDFs and authorize multiple MaxCompute projects to use them, we recommend that you ensure the UDFs are compatible with both Python 2 and Python 3.

UDF examples