Python(boto3)中的 DynamoDB PutItem

put_item 會寫入一整個項目,並取代任何具有相同主索引鍵的既有項目(以項目為單位的動作談了它與 update_item 的差別)。用低階 client 時,每一個屬性都以 DynamoDB JSON 傳入,而 boto3 會在任何東西送出去之前,先在本機檢查那個結構。

程式碼

import boto3
from botocore.exceptions import ClientError

client = boto3.client("dynamodb")

try:
    client.put_item(
        TableName="Music",
        Item={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}, "AlbumTitle": {"S": "Danzon"}, "Year": {"N": "1994"}, "Awards": {"N": "0"}},
        ConditionExpression="attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)",
        ExpressionAttributeNames={"#cond0": "Artist", "#cond1": "SongTitle"},
    )
    print("Song written")
except ClientError as err:
    if err.response["Error"]["Code"] == "ConditionalCheckFailedException":
        print("A song with that key already exists — not overwritten")
    else:
        raise

說明

{"N": 1994} 根本到不了 AWS,而 except ClientError 也攔不到它。botocore 會先拿自己的服務模型驗證請求,而在 N 型別要字串的地方放一個 Python int,就會在那裡失敗:

ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.Year.N, value: 1994, type: <class 'int'>, valid types: <class 'str'>

ParamValidationError 繼承自 BotoCoreError,不是 ClientError,所以上面程式碼裡的處理器會直接放它過去。這通常正是你要的,因為它是臭蟲而不是商業結果,但這也代表在寫入外面包一層 try/except ClientError 並不是萬用網。好處是這個錯誤會指名精確的路徑 Item.Year.N,對除錯來說勝過伺服器端的 ValidationException。更多內容請見「Parameter validation failed」

條件失敗時的完整樣貌。把同一次條件式 put 跑兩次並印出例外上的所有東西,得到:

type(e).__name__                              ConditionalCheckFailedException
e.response["Error"]["Code"]                   ConditionalCheckFailedException
e.response["Error"]["Message"]                The conditional request failed
e.response["ResponseMetadata"]["HTTPStatusCode"]  400
str(e)                                        An error occurred (ConditionalCheckFailedException) when calling the PutItem operation: The conditional request failed

由此可得兩件事。在 botocore 1.43.58 上,這個物件是一個模型化的子類別,所以 except client.exceptions.ConditionalCheckFailedException 和程式碼裡用的 err.response["Error"]["Code"] 檢查都行得通;挑一種並且維持一致。另外,str(e) 是一句格式化過的句子,不是服務的訊息,所以絕對不要拿它去跟字面值比較。

條件失敗一樣要付寫入的錢。AWS 說:"if the expression evaluates to false, DynamoDB still consumes write capacity units from the table"(2026-07-28 取得)。一個只建立不覆寫的重試迴圈,每一次被回絕的嘗試都要付費。給你一個量級感:成功寫入一個約 15 KB 的項目,在 ReturnConsumedCapacity="TOTAL" 下回報 "CapacityUnits": 15;寫入是每 1 KB 無條件進位,不是讀取用的那個 4 KB。

resource API 是另一份契約,而 float 就是你發現這件事的地方boto3.resource("dynamodb").Table("Music").put_item(Item={...}) 收的是純 Python 並替你 marshal,但它直接拒絕二進位浮點數:

TypeError: Float types are not supported. Use Decimal types instead.

請把值包成 decimal.Decimal("4.5"),而且要從字串建,不要從 float 建,否則誤差在 Decimal 看到它之前就已經烙進去了。用同一套 API 讀回來時,每個數字都會是 Decimal,這對你的程式碼是真正的改變,不是格式細節。請見「Float types are not supported」

混用這兩套 API 是兩邊都不會警告你的陷阱。低階 client 會很樂意接受 {"N": "1.5"},而 resource API 早就會把這個值當成 float 回絕掉。一份用其中一套寫、用另一套讀的程式碼庫,會從一批進去時從沒經過 Decimal 的資料裡拿回 Decimal

那些 #cond0 別名不是裝飾。它們透過 ExpressionAttributeNames 解析成 ArtistSongTitle。直接寫屬性名稱一路都沒問題,直到其中一個撞上保留字,然後運算式就在一個你根本沒動過的名稱上失敗。

改用視覺化操作

條件運算式是手寫時最先出錯的地方,因為寫錯的條件是以一次被回絕的寫入呈現,而不是語法錯誤。免費的 DynamoDB Expression Builder 會把 ConditionExpression 連同它的名稱與值對應表一起組好,並產出可直接貼上的 boto3 呼叫。

想對你自己的資料表寫入與編輯項目 — 每個屬性一個欄位、型別選擇器、把結果複製回去變成 boto3 程式碼 — 請下載 DynoTable

相關指南

參考資料

已於 2026-07-28 以 boto3 1.43.58/botocore 1.43.58,對照連接埠 9000 上的 DynamoDB Local(amazon/dynamodb-local)重現。上方的例外文字、回應欄位與容量讀數都是擷取到的輸出,逐字照錄。

不必透過主控台就能操作 DynamoDB

一款快速的 DynamoDB 桌面用戶端,可執行 DynamoDB 無法執行的真正 SQL — JOINs、GROUP BY、聚合 — 並支援視覺化編輯與使用你自己的 Bedrock 金鑰的 AI 代理。

30 天免費試用,無需信用卡 — 之後為無時間限制的免費方案。