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 解析成 Artist/SongTitle。直接寫屬性名稱一路都沒問題,直到其中一個撞上保留字,然後運算式就在一個你根本沒動過的名稱上失敗。
改用視覺化操作
條件運算式是手寫時最先出錯的地方,因為寫錯的條件是以一次被回絕的寫入呈現,而不是語法錯誤。免費的 DynamoDB Expression Builder 會把 ConditionExpression 連同它的名稱與值對應表一起組好,並產出可直接貼上的 boto3 呼叫。
想對你自己的資料表寫入與編輯項目 — 每個屬性一個欄位、型別選擇器、把結果複製回去變成 boto3 程式碼 — 請下載 DynoTable。
相關指南
- DynamoDB 條件運算式 —
attribute_not_exists、樂觀鎖,以及更多。 - DynamoDB 資料型別 — 每一種屬性型別在 DynamoDB JSON 裡的寫法。
- DynamoDB ConditionalCheckFailedException — 當項目已存在時,只建立不覆寫的條件會丟出什麼。
- DynamoDB ValidationException — 項目或運算式格式錯誤時的統包錯誤。
參考資料
- PutItem — Amazon DynamoDB API Reference
- put_item — Boto3 DynamoDB.Client Reference
- Error handling — Boto3 Developer Guide
- Capacity unit consumption — Amazon DynamoDB Developer Guide
- Condition expressions — Amazon DynamoDB Developer Guide
已於 2026-07-28 以 boto3 1.43.58/botocore 1.43.58,對照連接埠 9000 上的 DynamoDB Local(amazon/dynamodb-local)重現。上方的例外文字、回應欄位與容量讀數都是擷取到的輸出,逐字照錄。