Python Pydantic Optional Fields: Defaults & default_factory
python pydantic optional fields defaults and default_factory: Learn how to define optional fields in Pydantic, the difference between default values and default_factor...
python pydantic optional fields defaults and default_factory requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When defining a Pydantic model, you often need fields that can be omitted. The way you declare an optional field—and whether you give it a default or a default_factory—changes how the model behaves during validation and how it handles mutable data. This article explains the difference between default and default_factory for optional fields in Python Pydantic, and when to use each.
What Does Optional Mean in Pydantic?
In Pydantic, an optional field is one that accepts None as a value. You declare it using Optional[T] from typing, which is equivalent to Union[T, None]. However, Optional[T] alone does not make the field optional in the sense of being omittable. A field declared as Optional[int] without a default is still required—it must be present in the input, but it may be None.
from typing import Optional from pydantic import BaseModel class User(BaseModel): nickname: Optional[str] # required, but can be None
If you try to instantiate User without nickname, Pydantic raises a validation error. To make the field truly optional (i.e., not required), you must provide a default value, typically None.
class User(BaseModel): nickname: Optional[str] = None # not required, defaults to None
Now User() is valid and nickname is None. This is the most common way to declare an optional field, but it has a subtle limitation when the default is a mutable object like a list or dict.
Default Values vs default_factory
The default parameter in Pydantic's Field function (or simply assigning a value in the class body) sets a static default. This works fine for immutable types like int, str, or None. But if you use a mutable default directly, you risk sharing the same object across all instances of the model.
class ShoppingCart(BaseModel): items: list[str] = [] # dangerous: shared list across instances
Every ShoppingCart instance that doesn't provide items will reference the same list object. Mutating one cart's items affects all others. This is the classic mutable default argument problem, and Pydantic explicitly disallows it by raising a ValueError at class definition time.
Instead, Pydantic provides default_factory. This parameter takes a callable that is invoked each time a new instance is created and the field is not supplied. The callable returns a fresh object, avoiding shared state.
from pydantic import BaseModel, Field class ShoppingCart(BaseModel): items: list[str] = Field(default_factory=list)
Now each cart gets its own empty list. The same pattern works for dicts, sets, and any custom mutable type.
Using default_factory for Mutable Defaults
default_factory is not limited to built-in types. You can pass any zero-argument callable, including a lambda or a function you define.
from datetime import datetime, timezone def utc_now() -> datetime: return datetime.now(timezone.utc) class Event(BaseModel): created_at: datetime = Field(default_factory=utc_now)
Here, each Event instance gets the current UTC time at creation. If you used default=datetime.now(timezone.utc), the timestamp would be fixed at class definition time, which is rarely what you want.
default_factory also works with Pydantic's Field for more complex defaults, such as a list of dictionaries:
class Order(BaseModel): line_items: list[dict] = Field(default_factory=list)
When the field is omitted, the factory is called. When the field is provided, the factory is not called—the provided value is validated and used.
Common Pitfalls and Edge Cases
One common mistake is using default_factory on a required field. If you give a field a default_factory, it becomes optional—Pydantic will not require it. If you want the field to be required but still use a factory for some reason, you cannot; the presence of any default makes the field optional. In that case, you should not use a default at all.
Another edge case is combining Optional[T] with default_factory. For example:
class Config(BaseModel): tags: Optional[list[str]] = Field(default_factory=list)
This field is optional and defaults to an empty list, not None. If you want the default to be None, use default=None instead. The choice depends on your domain: do you want a missing field to become an empty collection, or a None that signals absence?
Also, note that default_factory is called at instance creation, not at class definition. This means it can depend on runtime state, such as environment variables or current time, without causing shared state issues.
Runtime Behavior and Validation
When you instantiate a Pydantic model, validation runs on the provided data. For fields with a default_factory, the factory is invoked only if the field is absent from the input. If the field is present, its value is validated against the declared type. This means the factory is not called for every instance—only when needed.
The factory itself is not validated; its return value is. So if your factory returns a wrong type, Pydantic will raise a validation error. This is useful because it catches mistakes in the factory early.
Another subtle behavior: default_factory is evaluated lazily per instance. This is different from default, which is evaluated once at class definition. For immutable defaults, the difference is negligible. For mutable or dynamic defaults, default_factory is the only correct choice.
Performance and Maintainability Considerations
Using default_factory has a tiny runtime cost: the callable is invoked each time a new instance is created without that field. For most applications, this overhead is negligible. However, if you have a model with many fields that each use a factory, and you create millions of instances, the cumulative cost can matter. In such cases, consider whether a simple immutable default (like None or a constant) is acceptable.
From a maintainability perspective, default_factory makes the default behavior explicit and testable. You can unit-test the factory independently. It also avoids the shared-mutable-state bug, which is a common source of subtle production issues. When reviewing code, prefer default_factory for any mutable default, and reserve default for immutable values.
One more consideration: if your factory is expensive (e.g., it reads from a database), be aware that it runs on every instance creation. You might want to cache the result if the value is truly constant across instances, but then you'd lose the per-instance freshness. The right tradeoff depends on your use case. For most models, a simple list or dict factory is cheap and safe.