sloria: Merging this because I don't want this bug and the bug in #1160 in the wild for too long.
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5d010b98..f547143c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,14 @@
 Changelog
 ---------
 
+3.0.4 (unreleased)
+++++++++++++++++++
+
+Bug fixes:
+
+- Fix propagating dot-delimited `only` and `exclude` parameters to nested schema instances (:issue:`1384`).
+- Includes bug fix from 2.20.4 (:issue:`1160`).
+
 3.0.3 (2019-09-04)
 ++++++++++++++++++
 
diff --git a/src/marshmallow/fields.py b/src/marshmallow/fields.py
index 2858cde7..8c5b8568 100644
--- a/src/marshmallow/fields.py
+++ b/src/marshmallow/fields.py
@@ -468,8 +468,17 @@ class Nested(Field):
             # Inherit context from parent.
             context = getattr(self.parent, "context", {})
             if isinstance(self.nested, SchemaABC):
-                self._schema = self.nested
+                self._schema = copy.deepcopy(self.nested)
                 self._schema.context.update(context)
+                # Respect only and exclude passed from parent and re-initialize fields
+                set_class = self._schema.set_class
+                if self.only is not None:
+                    original = self._schema.only
+                    self._schema.only = set_class(self.only).intersection(original)
+                if self.exclude:
+                    original = self._schema.exclude
+                    self._schema.exclude = set_class(self.exclude).union(original)
+                self._schema._init_fields()
             else:
                 if isinstance(self.nested, type) and issubclass(self.nested, SchemaABC):
                     schema_class = self.nested
diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py
index afd2f5db..7614f575 100644
--- a/src/marshmallow/schema.py
+++ b/src/marshmallow/schema.py
@@ -378,16 +378,10 @@ class BaseSchema(base.SchemaABC):
         self.context = context or {}
         self._normalize_nested_options()
         #: Dictionary mapping field_names -> :class:`Field` objects
-        self.fields = self._init_fields()
-        self.dump_fields, self.load_fields = self.dict_class(), self.dict_class()
-        for field_name, field_obj in self.fields.items():
-            if field_obj.load_only:
-                self.load_fields[field_name] = field_obj
-            elif field_obj.dump_only:
-                self.dump_fields[field_name] = field_obj
-            else:
-                self.load_fields[field_name] = field_obj
-                self.dump_fields[field_name] = field_obj
+        self.fields = None
+        self.load_fields = None
+        self.dump_fields = None
+        self._init_fields()
         messages = {}
         messages.update(self._default_error_messages)
         for cls in reversed(self.__class__.__mro__):
@@ -757,6 +751,7 @@ class BaseSchema(base.SchemaABC):
         self, data, *, many=None, partial=None, unknown=None, postprocess=True
     ):
         """Deserialize `data`, returning the deserialized result.
+        This method is private API.
 
         :param data: The data to deserialize.
         :param bool many: Whether to deserialize `data` as a collection. If `None`, the
@@ -844,7 +839,9 @@ class BaseSchema(base.SchemaABC):
         return result
 
     def _normalize_nested_options(self):
-        """Apply then flatten nested schema options"""
+        """Apply then flatten nested schema options.
+        This method is private API.
+        """
         if self.only is not None:
             # Apply the only option to nested fields.
             self.__apply_nested_option("only", self.only, "intersection")
@@ -878,7 +875,9 @@ class BaseSchema(base.SchemaABC):
             setattr(self.declared_fields[key], option_name, new_options)
 
     def _init_fields(self):
-        """Update fields based on schema options."""
+        """Update self.fields, self.load_fields, and self.dump_fields based on schema options.
+        This method is private API.
+        """
         if self.opts.fields:
             available_field_names = self.set_class(self.opts.fields)
         else:
@@ -913,10 +912,19 @@ class BaseSchema(base.SchemaABC):
             self._bind_field(field_name, field_obj)
             fields_dict[field_name] = field_obj
 
+        load_fields, dump_fields = self.dict_class(), self.dict_class()
+        for field_name, field_obj in fields_dict.items():
+            if field_obj.load_only:
+                load_fields[field_name] = field_obj
+            elif field_obj.dump_only:
+                dump_fields[field_name] = field_obj
+            else:
+                load_fields[field_name] = field_obj
+                dump_fields[field_name] = field_obj
+
         dump_data_keys = [
             field_obj.data_key if field_obj.data_key is not None else name
-            for name, field_obj in fields_dict.items()
-            if not field_obj.load_only
+            for name, field_obj in dump_fields.items()
         ]
         if len(dump_data_keys) != len(set(dump_data_keys)):
             data_keys_duplicates = {
@@ -928,12 +936,7 @@ class BaseSchema(base.SchemaABC):
                 "Check the following field names and "
                 "data_key arguments: {}".format(list(data_keys_duplicates))
             )
-
-        load_attributes = [
-            obj.attribute or name
-            for name, obj in fields_dict.items()
-            if not obj.dump_only
-        ]
+        load_attributes = [obj.attribute or name for name, obj in load_fields.items()]
         if len(load_attributes) != len(set(load_attributes)):
             attributes_duplicates = {
                 x for x in load_attributes if load_attributes.count(x) > 1
@@ -945,7 +948,9 @@ class BaseSchema(base.SchemaABC):
                 "attribute arguments: {}".format(list(attributes_duplicates))
             )
 
-        return fields_dict
+        self.fields = fields_dict
+        self.dump_fields = dump_fields
+        self.load_fields = load_fields
 
     def on_bind_field(self, field_name, field_obj):
         """Hook to modify a field when it is bound to the `Schema`.

Dotted `only` and `exclude` not working for nested schema instances
#1229 fixed propagation of `only` and `exclude` for nested schema classes but no instances.
Dotted strings passed to `only` and `exclude` are not respected when a schema instance is nested.


```python
from marshmallow import Schema, fields


class Child(Schema):
    name = fields.String()
    age = fields.Integer()


class Parent(Schema):
    favorite = fields.Nested(Child, only=("name", "age"))
    children = fields.List(fields.Nested(Child, only=("name", "age")))


class Parent2(Schema):
    favorite = fields.Nested(Child(only=("name", "age")))
    children = fields.List(fields.Nested(Child(only=("name", "age"))))


family = {
    "favorite": {"name": "Lily", "age": 15},
    "children": [{"name": "Tommy", "age": 12}, {"name": "Lily", "age": 15}],
}
# should be the same
print(Parent(only=("favorite.age", "children.name")).dump(family))
print(Parent2(only=("favorite.age", "children.name")).dump(family))
```

Expected output:

```
{'children': [{'name': 'Tommy'}, {'name': 'Lily'}], 'favorite': {'age': 15}}
{'children': [{'name': 'Tommy'}, {'name': 'Lily'}], 'favorite': {'age': 15}}
```

Actual output:

```
{'favorite': {'age': 15}, 'children': [{'name': 'Tommy'}, {'name': 'Lily'}]}
{'favorite': {'age': 15, 'name': 'Lily'}, 'children': [{'age': 12, 'name': 'Tommy'}, {'age': 15, 'name': 'Lily'}]}
```

diff --git a/tests/test_fields.py b/tests/test_fields.py
index 751f7cd9..f4cb3bd2 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -294,10 +294,15 @@ class TestListNested:
         assert getattr(schema.fields["children"].inner.schema, param) == {"name"}
 
     @pytest.mark.parametrize(
-        ("param", "expected"),
-        (("only", {"name"}), ("exclude", {"name", "surname", "age"})),
+        ("param", "expected_attribute", "expected_dump"),
+        (
+            ("only", {"name"}, {"children": [{"name": "Lily"}]}),
+            ("exclude", {"name", "surname", "age"}, {"children": [{}]}),
+        ),
     )
-    def test_list_nested_only_and_exclude_merged_with_nested(self, param, expected):
+    def test_list_nested_class_only_and_exclude_merged_with_nested(
+        self, param, expected_attribute, expected_dump
+    ):
         class Child(Schema):
             name = fields.String()
             surname = fields.String()
@@ -307,7 +312,70 @@ class TestListNested:
             children = fields.List(fields.Nested(Child, **{param: ("name", "surname")}))
 
         schema = Family(**{param: ["children.name", "children.age"]})
-        assert getattr(schema.fields["children"].inner, param) == expected
+        assert getattr(schema.fields["children"].inner, param) == expected_attribute
+
+        family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
+        assert schema.dump(family) == expected_dump
+
+    def test_list_nested_class_multiple_dumps(self):
+        class Child(Schema):
+            name = fields.String()
+            surname = fields.String()
+            age = fields.Integer()
+
+        class Family(Schema):
+            children = fields.List(fields.Nested(Child, only=("name", "age")))
+
+        family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
+        assert Family(only=("children.age",)).dump(family) == {
+            "children": [{"age": 15}]
+        }
+        assert Family(only=("children.name",)).dump(family) == {
+            "children": [{"name": "Lily"}]
+        }
+
+    @pytest.mark.parametrize(
+        ("param", "expected_attribute", "expected_dump"),
+        (
+            ("only", {"name"}, {"children": [{"name": "Lily"}]}),
+            ("exclude", {"name", "surname", "age"}, {"children": [{}]}),
+        ),
+    )
+    def test_list_nested_instance_only_and_exclude_merged_with_nested(
+        self, param, expected_attribute, expected_dump
+    ):
+        class Child(Schema):
+            name = fields.String()
+            surname = fields.String()
+            age = fields.Integer()
+
+        class Family(Schema):
+            children = fields.List(fields.Nested(Child(**{param: ("name", "surname")})))
+
+        schema = Family(**{param: ["children.name", "children.age"]})
+        assert (
+            getattr(schema.fields["children"].inner.schema, param) == expected_attribute
+        )
+
+        family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
+        assert schema.dump(family) == expected_dump
+
+    def test_list_nested_instance_multiple_dumps(self):
+        class Child(Schema):
+            name = fields.String()
+            surname = fields.String()
+            age = fields.Integer()
+
+        class Family(Schema):
+            children = fields.List(fields.Nested(Child(only=("name", "age"))))
+
+        family = {"children": [{"name": "Lily", "surname": "Martinez", "age": 15}]}
+        assert Family(only=("children.age",)).dump(family) == {
+            "children": [{"age": 15}]
+        }
+        assert Family(only=("children.name",)).dump(family) == {
+            "children": [{"name": "Lily"}]
+        }
 
     def test_list_nested_partial_propagated_to_nested(self):
         class Child(Schema):

pip install -e .[dev]
parse_log_pytest
requirements.txt
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aspy.yaml==1.3.0
attrs==25.3.0
cachetools==5.5.2
cfgv==3.4.0
chardet==5.2.0
colorama==0.4.6
distlib==0.3.9
entrypoints==0.3
exceptiongroup==1.2.2
filelock==3.18.0
flake8==3.7.8
flake8-bugbear==19.8.0
identify==2.6.9
iniconfig==2.1.0
-e git+https://github.com/marshmallow-code/marshmallow.git@8b3a32614fd4a74e93e9a63a042e74c1fea34466#egg=marshmallow
mccabe==0.6.1
nodeenv==1.9.1
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pre-commit==1.21.0
pycodestyle==2.5.0
pyflakes==2.1.1
pyproject-api==1.9.0
pytest==8.3.5
pytz==2025.2
PyYAML==6.0.2
simplejson==3.20.1
six==1.17.0
toml==0.10.2
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
virtualenv==20.29.3

name: marshmallow
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aspy-yaml==1.3.0
      - attrs==25.3.0
      - cachetools==5.5.2
      - cfgv==3.4.0
      - chardet==5.2.0
      - colorama==0.4.6
      - distlib==0.3.9
      - entrypoints==0.3
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==3.7.8
      - flake8-bugbear==19.8.0
      - identify==2.6.9
      - iniconfig==2.1.0
      - marshmallow==3.0.3
      - mccabe==0.6.1
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pre-commit==1.21.0
      - pycodestyle==2.5.0
      - pyflakes==2.1.1
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytz==2025.2
      - pyyaml==6.0.2
      - simplejson==3.20.1
      - six==1.17.0
      - toml==0.10.2
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/marshmallow

tests/test_fields.py::TestListNested::test_list_nested_instance_only_and_exclude_merged_with_nested[only-expected_attribute0-expected_dump0] tests/test_fields.py::TestListNested::test_list_nested_instance_only_and_exclude_merged_with_nested[exclude-expected_attribute1-expected_dump1] tests/test_fields.py::TestListNested::test_list_nested_instance_multiple_dumps
tests/test_fields.py::test_field_aliases[Integer-Integer] tests/test_fields.py::test_field_aliases[String-String] tests/test_fields.py::test_field_aliases[Boolean-Boolean] tests/test_fields.py::test_field_aliases[Url-Url] tests/test_fields.py::TestField::test_repr tests/test_fields.py::TestField::test_error_raised_if_uncallable_validator_passed tests/test_fields.py::TestField::test_error_raised_if_missing_is_set_on_required_field tests/test_fields.py::TestField::test_custom_field_receives_attr_and_obj tests/test_fields.py::TestField::test_custom_field_receives_data_key_if_set tests/test_fields.py::TestField::test_custom_field_follows_data_key_if_set tests/test_fields.py::TestParentAndName::test_simple_field_parent_and_name tests/test_fields.py::TestParentAndName::test_unbound_field_root_returns_none tests/test_fields.py::TestParentAndName::test_list_field_inner_parent_and_name tests/test_fields.py::TestParentAndName::test_tuple_field_inner_parent_and_name tests/test_fields.py::TestParentAndName::test_mapping_field_inner_parent_and_name tests/test_fields.py::TestParentAndName::test_simple_field_root tests/test_fields.py::TestParentAndName::test_list_field_inner_root tests/test_fields.py::TestParentAndName::test_tuple_field_inner_root tests/test_fields.py::TestParentAndName::test_list_root_inheritance tests/test_fields.py::TestParentAndName::test_dict_root_inheritance tests/test_fields.py::TestParentAndName::test_datetime_list_inner_format tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[String] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Integer] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Boolean] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Float] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Number] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[DateTime] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Time] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Date] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[TimeDelta] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Dict] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Url] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Email] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[UUID] tests/test_fields.py::TestMetadata::test_extra_metadata_may_be_added_to_field[Decimal] tests/test_fields.py::TestErrorMessages::test_default_error_messages_get_merged_with_parent_error_messages_cstm_msg tests/test_fields.py::TestErrorMessages::test_default_error_messages_get_merged_with_parent_error_messages tests/test_fields.py::TestErrorMessages::test_make_error[required-Missing tests/test_fields.py::TestErrorMessages::test_make_error[null-Field tests/test_fields.py::TestErrorMessages::test_make_error[custom-Custom tests/test_fields.py::TestErrorMessages::test_make_error[validator_failed-Invalid tests/test_fields.py::TestErrorMessages::test_fail[required-Missing tests/test_fields.py::TestErrorMessages::test_fail[null-Field tests/test_fields.py::TestErrorMessages::test_fail[custom-Custom tests/test_fields.py::TestErrorMessages::test_fail[validator_failed-Invalid tests/test_fields.py::TestErrorMessages::test_make_error_key_doesnt_exist tests/test_fields.py::TestNestedField::test_nested_only_and_exclude_as_string[only] tests/test_fields.py::TestNestedField::test_nested_only_and_exclude_as_string[exclude] tests/test_fields.py::TestNestedField::test_nested_unknown_override[None-exclude] tests/test_fields.py::TestNestedField::test_nested_unknown_override[None-include] tests/test_fields.py::TestNestedField::test_nested_unknown_override[None-raise] tests/test_fields.py::TestNestedField::test_nested_unknown_override[exclude-exclude] tests/test_fields.py::TestNestedField::test_nested_unknown_override[exclude-include] tests/test_fields.py::TestNestedField::test_nested_unknown_override[exclude-raise] tests/test_fields.py::TestNestedField::test_nested_unknown_override[include-exclude] tests/test_fields.py::TestNestedField::test_nested_unknown_override[include-include] tests/test_fields.py::TestNestedField::test_nested_unknown_override[include-raise] tests/test_fields.py::TestNestedField::test_nested_unknown_override[raise-exclude] tests/test_fields.py::TestNestedField::test_nested_unknown_override[raise-include] tests/test_fields.py::TestNestedField::test_nested_unknown_override[raise-raise] tests/test_fields.py::TestListNested::test_list_nested_only_exclude_dump_only_load_only_propagated_to_nested[only] tests/test_fields.py::TestListNested::test_list_nested_only_exclude_dump_only_load_only_propagated_to_nested[exclude] tests/test_fields.py::TestListNested::test_list_nested_only_exclude_dump_only_load_only_propagated_to_nested[dump_only] tests/test_fields.py::TestListNested::test_list_nested_only_exclude_dump_only_load_only_propagated_to_nested[load_only] tests/test_fields.py::TestListNested::test_list_nested_class_only_and_exclude_merged_with_nested[only-expected_attribute0-expected_dump0] tests/test_fields.py::TestListNested::test_list_nested_class_only_and_exclude_merged_with_nested[exclude-expected_attribute1-expected_dump1] tests/test_fields.py::TestListNested::test_list_nested_class_multiple_dumps tests/test_fields.py::TestListNested::test_list_nested_partial_propagated_to_nested tests/test_fields.py::TestTupleNested::test_tuple_nested_only_exclude_dump_only_load_only_propagated_to_nested[dump_only] tests/test_fields.py::TestTupleNested::test_tuple_nested_only_exclude_dump_only_load_only_propagated_to_nested[load_only] tests/test_fields.py::TestTupleNested::test_tuple_nested_partial_propagated_to_nested tests/test_fields.py::TestDictNested::test_dict_nested_only_exclude_dump_only_load_only_propagated_to_nested[only] tests/test_fields.py::TestDictNested::test_dict_nested_only_exclude_dump_only_load_only_propagated_to_nested[exclude] tests/test_fields.py::TestDictNested::test_dict_nested_only_exclude_dump_only_load_only_propagated_to_nested[dump_only] tests/test_fields.py::TestDictNested::test_dict_nested_only_exclude_dump_only_load_only_propagated_to_nested[load_only] tests/test_fields.py::TestDictNested::test_dict_nested_only_and_exclude_merged_with_nested[only-expected0] tests/test_fields.py::TestDictNested::test_dict_nested_only_and_exclude_merged_with_nested[exclude-expected1] tests/test_fields.py::TestDictNested::test_dict_nested_partial_propagated_to_nested
CHANGELOG.rst src/marshmallow/fields.py src/marshmallow/schema.py
CHANGELOG.rst src/marshmallow/fields.py src/marshmallow/schema.py