Skip to content

Serverless Python — Building Lambda Functions with Python

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Serverless Python. We cover key concepts, practical examples, and best practices to help you master this topic.

Python is one of the most popular runtimes for AWS Lambda, offering rich libraries for data processing, HTTP requests, and AWS service integration with minimal cold start overhead.

What You'll Learn

By the end of this lesson you will understand Python-specific Lambda patterns, dependency management, using libraries like boto3, requests, and Pillow, optimizing cold starts, and testing Python functions.

Why It Matters

Python's extensive ecosystem makes it powerful for serverless -- from data processing with Pandas to HTTP clients with requests to AWS integration with boto3. Understanding Python-specific patterns ensures efficient, cost-effective functions.

Real-World Use

DodaTech's file scanning Lambda functions use Python with boto3 for S3 interactions and custom malware signature matching. Python's concise syntax keeps the codebase small and maintainable.

# serverless_python.py
# Python Lambda basics

import json
import boto3
import os

def lambda_handler(event, context):
    print(f"Python version: {os.environ.get('AWS_EXECUTION_ENV', 'local')}")
    print(f"Event: {json.dumps(event)[:100]}...")
    
    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Hello from Python Lambda", "runtime": "Python 3.9"})
    }

result = lambda_handler({"test": True}, None)
print(result["body"])

Expected output:

Python version: local
Event: {"test": true}...
{"message": "Hello from Python Lambda", "runtime": "Python 3.9"}

Dependency Management

Use the serverless-python-requirements plugin to bundle dependencies. Use layers for shared libraries.

# serverless.yml with Python requirements
plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true  # Use Docker for native binaries
    slim: true           # Remove tests and caches
    layer: true          # Create a separate layer for dependencies

functions:
  processImage:
    handler: handlers/image.process
    layers:
      - {Ref: PythonRequirementsLambdaLayer}
# handlers/image.py
# Using Pillow from a layer

import json

def process(event, context):
    print("Image processing function initialized")
    print("Pillow library available from Python requirements layer")
    
    for record in event.get("Records", []):
        key = record["s3"]["object"]["key"]
        print(f"Processing image: {key}")
    
    return {"statusCode": 200, "body": json.dumps({"processed": len(event.get("Records", []))})}

test = {"Records": [{"s3": {"object": {"key": "photo.jpg"}}}]}
print(process(test, None)["body"])

Working with boto3

boto3 is the AWS SDK for Python, pre-installed in the Lambda runtime.

# boto3_examples.py
# Using boto3 in Lambda

import json
import boto3
from boto3.dynamodb.conditions import Key

# Initialize clients outside handler
s3 = boto3.client("s3")
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ.get("TABLE_NAME", "users"))

import os

def lambda_handler(event, context):
    # List S3 objects
    bucket = os.environ.get("BUCKET", "my-bucket")
    response = s3.list_objects_v2(Bucket=bucket, MaxKeys=10)
    print(f"S3 objects in {bucket}: {response.get('KeyCount', 0)}")
    
    # Query DynamoDB
    user_id = event.get("pathParameters", {}).get("id")
    if user_id:
        result = table.query(
            KeyConditionExpression=Key("pk").eq(f"USER#{user_id}")
        )
        print(f"DynamoDB result: {result.get('Items', [])}")
    
    return {"statusCode": 200, "body": json.dumps({"status": "ok"})}

os.environ["TABLE_NAME"] = "users"
os.environ["BUCKET"] = "my-bucket"
print(lambda_handler({"pathParameters": {"id": "1"}}, None)["body"])

Common Mistakes

  1. Forgetting to use json.dumps on return values: Lambda expects string bodies. Python dicts must be serialized with json.dumps.

  2. Not handling encoding issues: External data may have unexpected encodings. Use try/except with encoding detection.

  3. Using Pandas without layer optimization: Pandas is large (50MB+). Use a dedicated Lambda layer and consider alternatives like polars for smaller deployments.

  4. Ignoring Python version compatibility: Lambda supports specific Python versions. Use the same version locally as in production.

  5. Not using virtual environments for local testing: Always use venv or conda to match the Lambda runtime environment.

Practice Questions

  1. What is the default Python runtime version for AWS Lambda? AWS Lambda supports Python 3.9, 3.10, 3.11, 3.12, and 3.13.

  2. How do you bundle Python dependencies for Lambda? Use serverless-python-requirements plugin, pip install -t, or container images.

  3. Why use slim mode for Python requirements? It removes test files, caches, and .pyc files from the deployment package, reducing size and cold start time.

  4. How do you handle binary data in Python Lambda? Use base64 encoding for JSON responses or set binary content types in API Gateway.

  5. Challenge: Create a Python Lambda function that reads a CSV from S3, processes it with the csv module, and stores results in DynamoDB.

FAQ

Can I use async/await in Python Lambda?

Yes. Define your handler as async def and use await for async operations.

Does Lambda support Python type hints?

Yes. Type hints are supported at the language level and improve code quality.

How do I use environment variables in Python?

Import os and use os.environ.get('KEY', 'default').

Can I use Python generators in Lambda?

Yes, but the entire result must fit in memory. Generators are useful for streaming processing.

How do I test Python Lambda functions locally?

Create a test event dict and call your handler function directly with a mock context object.

Mini Project

Create a Python Lambda function that uses boto3 to query DynamoDB for user data, processes it, and returns a formatted response.

import json
import os

def lambda_handler(event, context):
    pk = event.get("pk", "USER#1")
    print(f"Querying DynamoDB for {pk}")
    
    result = query_dynamodb(pk)
    user = result.get("Item", {})
    
    return {
        "statusCode": 200,
        "body": json.dumps({
            "userId": user.get("id"),
            "name": user.get("name"),
            "email": user.get("email")
        })
    }

def query_dynamodb(pk):
    return {"Item": {"id": "1", "name": "Alice", "email": "alice@example.com"}}

print(json.loads(lambda_handler({"pk": "USER#1"}, None)["body"]))

What's Next

Next: Serverless Node.js for Node.js patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro