Repository: pydantic/pydantic

Issue:
BaseModel.construct returns instances with None assigned to fields with a default_factory
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.6.1
            pydantic compiled: True
                 install path: /home/mihai/.virtualenvs/pydantic/lib/python3.8/site-packages/pydantic
               python version: 3.8.3 (default, May 17 2020, 18:15:42)  [GCC 10.1.0]
                     platform: Linux-5.7.7-1-ck-x86_64-with-glibc2.2.5
     optional deps. installed: ['devtools']
```

```py
from pydantic import BaseModel, Field
from typing import List

class Model(BaseModel):
    foo: List[int] = Field(default_factory=list)
    bar: str = 'Baz'

print(Model.construct()) 

# in pydantic   1.5.1 the result is: foo=[] bar='Baz'
# in pydantic  ^1.6.0 the result is: foo=None bar='Baz'
```

Hello, I'm using pydantic in an application and after updating to v1.6.1 from v1.5.1 I came across this situation which results in a couple of failures.

From the changelog and documentation it's not clear if this change was intended or not.


Discussion:
Hi @zoopp,
Well we made some changes on `default_factory` to avoid calling it for nothing and avoid some side effects in #1504 and #1712 
But [this line](https://github.com/samuelcolvin/pydantic/pull/1504/files#diff-1ef405a6b5a2fa9f20ea7e1f92e4c38eR299) changes the default value that used to be set in v1.5.1 hence the `None`.
You could just use `Model()` instead of `Model.construct()` to fix this issue in the meantime.
I don't know what we really want in term of behaviour to
- avoid calling `default_factory` when not needed
- still have meaningful default values with `construct`
Thank you for the reply @PrettyWood,

It's okay, I can work around the issue until a consensus is reached on what the behavior should be. Speaking from my point of view, or more generally from a user of the library's point of view, I would say it would make sense that if `construct` initializes fields with default values then it should initialize fields with `default_factory` as well.

Would it make sense to call the factories inside the `construct` method as needed?


@zoopp I'll try to work on this bug soon. I agree the behaviour should be the same as calling `Model.__init__` in term of fields and set values. There is a very easy fix but it's quite ugly and I'd like to take a bit more time to see if we can find a cleaner solution

Resolving patch:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -215,7 +215,6 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
         fields: Dict[str, ModelField] = {}
         config = BaseConfig
         validators: 'ValidatorListDict' = {}
-        fields_defaults: Dict[str, Any] = {}
 
         pre_root_validators, post_root_validators = [], []
         for base in reversed(bases):
@@ -231,9 +230,6 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
         vg = ValidatorGroup(validators)
 
         for f in fields.values():
-            if not f.required:
-                fields_defaults[f.name] = f.default
-
             f.set_config(config)
             extra_validators = vg.get_validators(f.name)
             if extra_validators:
@@ -267,8 +263,6 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
                         class_validators=vg.get_validators(ann_name),
                         config=config,
                     )
-                    if not inferred.required:
-                        fields_defaults[ann_name] = inferred.default
 
             for var_name, value in namespace.items():
                 if (
@@ -291,8 +285,6 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
                             f'if you wish to change the type of this field, please use a type annotation'
                         )
                     fields[var_name] = inferred
-                    if not inferred.required:
-                        fields_defaults[var_name] = inferred.default
 
         _custom_root_type = ROOT_KEY in fields
         if _custom_root_type:
@@ -307,7 +299,6 @@ def __new__(mcs, name, bases, namespace, **kwargs):  # noqa C901
         new_namespace = {
             '__config__': config,
             '__fields__': fields,
-            '__field_defaults__': fields_defaults,
             '__validators__': vg.validators,
             '__pre_root_validators__': unique_list(pre_root_validators + pre_rv_new),
             '__post_root_validators__': unique_list(post_root_validators + post_rv_new),
@@ -327,7 +318,6 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
     if TYPE_CHECKING:
         # populated by the metaclass, defined here to help IDEs only
         __fields__: Dict[str, ModelField] = {}
-        __field_defaults__: Dict[str, Any] = {}
         __validators__: Dict[str, AnyCallable] = {}
         __pre_root_validators__: List[AnyCallable]
         __post_root_validators__: List[Tuple[bool, AnyCallable]]
@@ -528,7 +518,10 @@ def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, **valu
         Default values are respected, but no other validation is performed.
         """
         m = cls.__new__(cls)
-        object.__setattr__(m, '__dict__', {**smart_deepcopy(cls.__field_defaults__), **values})
+        # default field values
+        fields_values = {name: field.get_default() for name, field in cls.__fields__.items() if not field.required}
+        fields_values.update(values)
+        object.__setattr__(m, '__dict__', fields_values)
         if _fields_set is None:
             _fields_set = set(values.keys())
         object.__setattr__(m, '__fields_set__', _fields_set)
@@ -718,7 +711,7 @@ def _iter(
             if (
                 (allowed_keys is not None and field_key not in allowed_keys)
                 or (exclude_none and v is None)
-                or (exclude_defaults and self.__field_defaults__.get(field_key, _missing) == v)
+                or (exclude_defaults and getattr(self.__fields__.get(field_key), 'default', _missing) == v)
             ):
                 continue
             if by_alias and field_key in self.__fields__:


Test patch:
diff --git a/tests/test_construction.py b/tests/test_construction.py
--- a/tests/test_construction.py
+++ b/tests/test_construction.py
@@ -3,7 +3,7 @@
 
 import pytest
 
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 
 
 class Model(BaseModel):
@@ -275,3 +275,13 @@ class X(BaseModel):
     # deep['deep_thing'] gets modified
     assert x.deep['deep_thing'] == [1, 2, 3]
     assert y.deep['deep_thing'] == [1, 2, 3]
+
+
+def test_construct_default_factory():
+    class Model(BaseModel):
+        foo: List[int] = Field(default_factory=list)
+        bar: str = 'Baz'
+
+    m = Model.construct()
+    assert m.foo == []
+    assert m.bar == 'Baz'