如何在Python中创建含时间戳的类MongoDB ObjectId唯一ID
Great question! MongoDB's ObjectId is such a clever design—it's compact, unique, and quietly carries the timestamp of when the ID was created. Let's build something just like it using nothing but Python's built-in tools.
Core Breakdown of MongoDB's ObjectId
First, a quick recap of how ObjectId works to guide our implementation:
- It's a 12-byte binary value, represented as a 24-character hex string
- First 4 bytes: Unix timestamp (seconds since epoch, big-endian)
- Next 3 bytes: Machine identifier
- Next 2 bytes: Process ID
- Last 3 bytes: Auto-incrementing counter (prevents collisions in the same process)
We can simplify some parts while retaining the key properties: uniqueness and embedded timestamps.
Implementation 1: Full ObjectId Clone
This generates a 24-character hex ID identical to MongoDB's, using only standard libraries:
import time import secrets import struct import binascii def generate_mongo_like_id(): # 1. 4-byte timestamp (seconds since epoch, big-endian) timestamp = struct.pack('>I', int(time.time())) # 2. 5 bytes of cryptographically secure random data # Combines machine ID, PID, and randomness for cross-process/machine uniqueness random_component = secrets.token_bytes(5) # 3. 3-byte random counter (simpler than maintaining a global counter) # Secrets ensures extremely low collision risk here counter = secrets.token_bytes(3) # Combine all parts and convert to hex string id_bytes = timestamp + random_component + counter return binascii.hexlify(id_bytes).decode('utf-8')
Parsing the Timestamp from the ID
To extract the creation time later, use this helper:
def get_creation_timestamp(object_id_str): # Grab the first 8 hex chars (4 bytes) and convert back to integer timestamp_bytes = binascii.unhexlify(object_id_str[:8]) return struct.unpack('>I', timestamp_bytes)[0] # Example usage: created_id = generate_mongo_like_id() creation_time = get_creation_timestamp(created_id) print(f"ID created at: {time.ctime(creation_time)}")
Implementation 2: Shorter Timestamped IDs
If you want something more compact than 24 characters, we can use Base64 encoding instead of hex. This gives us a 12-character ID (without padding) while keeping the timestamp:
def generate_short_timestamped_id(): # 4-byte timestamp + 4-byte random data = 8 bytes total timestamp = struct.pack('>I', int(time.time())) random_part = secrets.token_bytes(4) # Encode to Base64 and strip padding/newlines return binascii.b2a_base64(timestamp + random_part).decode('utf-8').strip('=\n') # Parse timestamp from short ID: def get_timestamp_from_short_id(short_id): # Add back padding to make Base64 valid padded_id = short_id + '=' * (-len(short_id) % 4) decoded_bytes = binascii.a2b_base64(padded_id) return struct.unpack('>I', decoded_bytes[:4])[0]
Key Notes
- Secrets vs. Random: We use
secrets.token_bytes()instead ofrandombecause it's cryptographically secure, making collision risk extremely low—critical for unique IDs. - Counter vs. Random: For most applications, random bytes work fine. If you're in a high-concurrency scenario where you need strict ordering, replace the random counter with a thread-safe incrementing value (use
threading.Lockto protect it). - Python Version:
secretsis available in Python 3.6+. For older versions, useos.urandom()instead, though it's slightly less secure for sensitive use cases.
内容的提问来源于stack exchange,提问作者Jundiaius

