本文介绍Python运行环境的相关内容,包括打印日志、错误处理、内置模块、自定义模块以及调用外部命令。

背景信息

函数计算目前支持以下Python运行环境:

  • Python 2.7(runtime = python2.7)
  • Python 3.6(runtime = python3)

打印日志

函数stdout打印的内容会被收集到创建服务时指定的Logstore中,在这里有两种打印日志的方法:

  • 使用logging模块打印日志。该方法打印的每条日志中都包含时间、RequestId、日志级别等信息。
    import logging
    
    def my_handler(event, context):     
        logger = logging.getLogger()     
        logger.info('hello world')     
        return 'done'

    执行上面的代码输出的日志内容如下所示。

    message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad****e5 [INFO]   hello world
    说明 在这里推荐使用logging模块打印日志。因为该方法打印的日志自动包含RequestId信息,方便在出错的时候定位问题日志。
  • 直接使用print打印,把内容原样地输出到日志中。
    def my_handler(event, context):      
        print 'hello world'      
        return 'done'

    执行上面的代码输出的日志内容如下所示。

    message:hello world

错误处理

函数在执行过程如果抛出异常,那么函数计算会把异常捕获并将异常信息返回,示例如下所示。

def my_handler(event, context):   
    raise Exception('something is wrong')

调用函数时收到的响应如下所示。

{    
    "errorMessage": "something is wrong",    
    "errorType": "Exception",    
    "stackTrace": [       
        [            
            "File \"/code/index.py\"",           
            "line 2",            
            "in my_handler",            
            "raise Exception('something is wrong')"        
        ]    
    ]
}

发生异常时,函数调用的响应的HTTP header中会包含X-Fc-Error-Type: UnhandledInvocationError。函数计算的错误类型的更多信息,请参见错误处理

内置模块

除了Python的标准模块,函数计算的Python运行环境中还包含了一些常用模块,您可以直接引用,目前包含的模块如下所示。

模块名称 模块介绍
oss2 2.6.0 OSS SDK
tablestore 4.6.0 表格存储SDK
aliyun-fc2 2.1.0 函数计算SDK
aliyun-python-sdk-ecs 4.10.1 云服务器SDK
aliyun-python-sdk-vpc 3.0.2 专用网络SDK
aliyun-python-sdk-rds 2.1.4 云数据库SDK
aliyun-python-sdk-kms 2.5.0 密钥管理服务SDK
pydatahub 2.11.2 DataHub SDK
aliyun-mns 1.1.5 消息服务
aliyun-python-sdk-cdn 2.6.2 CDN服务
aliyun-python-sdk-ram 3.0.0 访问控制RAM
aliyun-python-sdk-sts 3.0.0 访问控制STS
aliyun-python-sdk-iot 7.8.0 物联网平台IOT
aliyun-log-python-sdk 0.6.38 日志服务SLS
wand 0.4.4 图片处理库
opencv 3.3.0.10 计算机视觉库
numpy 1.13.3 科学计算库
scipy 1.0.0 科学计算库
matplotlib 2.0.2 绘图库
scrapy 1.4.0 数据抓取库

访问OSS的示例代码如下所示。

import json
import oss2
def my_handler(event, context):
    evt = json.loads(event)
    creds = context.credentials
    # do not forget security_token
    auth = oss2.StsAuth(creds.access_key_id, creds.access_key_secret, creds.security_token)
    bucket = oss2.Bucket(auth, evt['endpoint'], evt['bucket'])
    bucket.put_object(evt['objectName'], evt['message'])
    return 'success'
说明 在上面的代码示例中,演示了如何使用临时密钥向OSS中上传一个文件。其他使用Python的第三方库的示例请参见fc-python-demo

自定义模块

如果您需要使用自定义的模块,则需要将自定义模块与代码一起打包上传。您可以通过pip包管理器进行依赖管理,如果您使用Funcraft部署应用,可以使用fun install命令来安装依赖。

  • 使用pip包管理器进行依赖管理

    通过pip install -t .命令安装依赖库至函数根目录下。上传代码库时将依赖库一同打包上传。下文以安装PyMySQL库为例进行详细介绍。

    1. 建立一个目录,用于存放代码和依赖模块。
      mkdir /tmp/code
    2. /tmp/code目录下安装依赖。
      cd /tmp/code
      pip install -t . PyMySQL
    3. 新建代码文件,例如/tmp/code/index.py,在代码中使用PyMySQL。
      import pymysql.cursors
      # Connect to the database
      connection = pymysql.connect(host='localhost',
                                   user='user',
                                   password='passwd',
                                   db='db',
                                   charset='utf8mb4',
                                   cursorclass=pymysql.cursors.DictCursor)
      def handler(event, context):
       with connection.cursor() as cursor:
           # Read a single record
           sql = "SELECT count(*) FROM `users`"
           cursor.execute(sql)
           result = cursor.fetchone()
           print(result)
           return result
    4. 打包上传。

      打包时,需要针对文件进行打包,而不是针对代码整体目录进行打包。打包完成后,入口函数文件需要位于包内的根目录。

      • 在Windows下打包时,可以进入函数代码目录,全选所有文件以后,单击鼠标右键,选择压缩为zip包,生成代码包。
      • 在Linux下打包时,通过调用zip命令时,将源文件指定为代码目录下的所有文件,实现生成部署代码包,例如zip code.zip /home/code/*

      打包后,您通过函数计算控制台代码执行页面,您可以选择OSS上传代码包上传方式上传代码包。

  • 使用fun install安装依赖。

    如果您使用Funcraft部署应用,可以使用fun install命令来安装依赖。关于使用Funcraft的部署应用请参见功能概览,关于fun install的详细信息请参见使用fun install安装第三方依赖

    下文以安装PyMySQL库为例进行详细介绍。

    1. 在函数根目录下初始化Funfile文件。
      fun install init
      ? Select a runtime   
        nodejs8   
        nodejs10   
        python2.7 
      ? python3   
        java8   
        php7.2   
        dotnetcore2.1 
       (Move up and down to reveal more choices)
    2. 选择python3后,会在当前目录生成一个名为Funfile的文件,编辑文件内容。
      RUNTIME python3
      RUN fun-install pip install PyMySQL
    3. 新建template.yml文件,例如/tmp/code/template.yml,详情请参见template.yml,内容如下。
      ROSTemplateFormatVersion: '2015-09-01'
      Transform: 'Aliyun::Serverless-2018-04-03'
      Resources:
      FunDemo:  
       Type: 'Aliyun::Serverless::Service' 
       pythondemo:    
         Type: 'Aliyun::Serverless::Function'   
         Properties:     
           Handler: index.handler     
           Runtime: python3     
           CodeUri: './'

      这个template.yml的含义如下:声明名为FunDemo的服务,在这个服务下,声明一个名为pythondemo的函数,配置函数入口为index.handler,函数的runtime为python3,并指定CodeUri为当前目录。在部署时,Fun会将CodeUri指定的目录打包上传。

    4. 在函数根目录下执行fun install命令安装依赖。
      fun install
      返回结果如下。
      using template: template.yml
      start installing function dependencies without docker
      
      building FunDemo/pythondemoFunfile exist, 
      Fun will use container to build forcely
      Step 1/2 : FROM registry.cn-beijing.aliyuncs.com/aliyunfc/runtime-python3.6:build-1.9.4
      ---> 702c91653452
      Step 2/2 : RUN fun-install pip install PyMySQL
      ---> Using cache
      ---> 8f59062f15bb
      sha256:8f59062f15bb7a86bd59b85e3b61bd0e4ed711c536fe0cd10fdefebc78eae152
      Successfully built 8f59062f15bb
      Successfully tagged fun-cache-a6f4221c-33c8-4050-93b8-015e42396475:latest
      copying function artifact to /Users/txd123/Desktop/express
      
      Install Success
      
      
      Tips for next step
      ======================
      * Invoke Event Function: fun local invoke
      * Invoke Http Function: fun local start
      * Build Http Function: fun build
      * Deploy Resources: fun deploy
    5. 执行以下命令部署应用。
      fun deploy -y

      返回结果如下。

      using template: template.yml
      using region: cn-hangzhou
      using accountId: ***********3743using accessKeyId: ***********Ptgk
      using timeout: 60
      
      Collecting your services information, in order to caculate devlopment changes...
      
      Resources Changes(Beta version! Only FC resources changes will be displayed):
      
      ┌────────────┬──────────────────────────────┬────────┬──────────┐
      │ Resource   │ ResourceType                 │ Action │ Property │
      ├────────────┼──────────────────────────────┼────────┼──────────┤
      │            │                              │        │ Handler  │
      │            │                              │        ├──────────┤
      │ pythondemo │ Aliyun::Serverless::Function │ Add    │ Runtime  │
      │            │                              │        ├──────────┤
      │            │                              │        │ CodeUri  │
      └────────────┴──────────────────────────────┴────────┴──────────┘
      
      Waiting for service FunDemo to be deployed...      
            Waiting for function pythondemo to be deployed...              
                    Waiting for packaging function pythondemo code...              
                    The function pythondemo has been packaged. A total of 51 files were compressed and the final size was 114.35 KB
            function pythondemo deploy success
      service FunDemo deploy success

    登录函数计算控制台,即可看到相关的服务、函数被创建成功,且触发执行可以返回正确的结果。

调用外部命令

您的函数可能会用到一些工具,而这些工具并不是用Python写的(例如Shell、C++ 、Go编译出来的可执行文件)。您仍然可以将这些工具与代码一起打包,然后在函数中通过运行外部命令的方法来使用这些工具。

说明 使用C、C++ 、Go编译出来的可执行文件,需要与函数计算的运行环境兼容。函数计算的Python运行环境如下所示。
  • Linux内核版本:Linux 4.4.24-2.al7.x86_64。
  • Docker基础镜像:docker pull python:2.7 ; docker pull python:3.6。

您可以使用Funcraft工具安装依赖,具体内容请参见使用fun install安装第三方依赖。下文以运行环境为python2.7,安装mysql-python(含有.so文件)为例。

可以直接在代码目录执行fun install --runtime python2.7 --package-type pip mysql-python命令 ,即可安装好依赖。

说明
  • 第一次执行这个命令由于要拉取镜像,可能要耗费不少时间,请耐心等待。
  • 依赖(含有mysql.so)会被装在.fun目录下,只需要在template.yml中通过CodeUri属性配置将.fun以及代码包含进来就可以了。

下面的代码演示了如何运行一个Shell脚本。

import os
import subprocess

def my_handler(event, context):    
    script_path = os.environ.get('FC_FUNC_CODE_PATH') + '/script.sh'    
    ret = subprocess.check_output(['bash', script_path])    
    return ret