diff --git a/django/forms/forms.py b/django/forms/forms.py
index d14037bfe9..40ed71ed5c 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -143,7 +143,13 @@ class BaseForm:
             'fields': ';'.join(self.fields),
         }
 
+    def _bound_items(self):
+        """Yield (name, bf) pairs, where bf is a BoundField object."""
+        for name in self.fields:
+            yield name, self[name]
+
     def __iter__(self):
+        """Yield the form's fields as BoundField objects."""
         for name in self.fields:
             yield self[name]
 
@@ -206,9 +212,9 @@ class BaseForm:
         top_errors = self.non_field_errors().copy()
         output, hidden_fields = [], []
 
-        for name, field in self.fields.items():
+        for name, bf in self._bound_items():
+            field = bf.field
             html_class_attr = ''
-            bf = self[name]
             bf_errors = self.error_class(bf.errors)
             if bf.is_hidden:
                 if bf_errors:
@@ -387,15 +393,12 @@ class BaseForm:
         self._post_clean()
 
     def _clean_fields(self):
-        for name, field in self.fields.items():
-            if field.disabled:
-                value = self.get_initial_for_field(field, name)
-            else:
-                value = self._field_data_value(field, self.add_prefix(name))
+        for name, bf in self._bound_items():
+            field = bf.field
+            value = bf.initial if field.disabled else bf.data
             try:
                 if isinstance(field, FileField):
-                    initial = self.get_initial_for_field(field, name)
-                    value = field.clean(value, initial)
+                    value = field.clean(value, bf.initial)
                 else:
                     value = field.clean(value)
                 self.cleaned_data[name] = value
@@ -437,24 +440,23 @@ class BaseForm:
     @cached_property
     def changed_data(self):
         data = []
-        for name, field in self.fields.items():
-            data_value = self._field_data_value(field, self.add_prefix(name))
+        for name, bf in self._bound_items():
+            field = bf.field
             if not field.show_hidden_initial:
                 # Use the BoundField's initial as this is the value passed to
                 # the widget.
-                initial_value = self[name].initial
+                initial_value = bf.initial
             else:
-                initial_prefixed_name = self.add_initial_prefix(name)
                 hidden_widget = field.hidden_widget()
                 try:
                     initial_value = field.to_python(
-                        self._widget_data_value(hidden_widget, initial_prefixed_name)
+                        self._widget_data_value(hidden_widget, bf.html_initial_name)
                     )
                 except ValidationError:
                     # Always assume data has changed if validation fails.
                     data.append(name)
                     continue
-            if field.has_changed(initial_value, data_value):
+            if field.has_changed(initial_value, bf.data):
                 data.append(name)
         return data
 
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index e1567f12ce..d1615f21f8 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -2112,15 +2112,47 @@ Password: <input type="password" name="password" required></li>
         self.assertEqual(unbound['hi_without_microsec'].value(), now_no_ms)
         self.assertEqual(unbound['ti_without_microsec'].value(), now_no_ms)
 
-    def test_datetime_clean_initial_callable_disabled(self):
-        now = datetime.datetime(2006, 10, 25, 14, 30, 45, 123456)
+    def get_datetime_form_with_callable_initial(self, disabled, microseconds=0):
+        class FakeTime:
+            def __init__(self):
+                self.elapsed_seconds = 0
+
+            def now(self):
+                self.elapsed_seconds += 1
+                return datetime.datetime(
+                    2006, 10, 25, 14, 30, 45 + self.elapsed_seconds,
+                    microseconds,
+                )
 
         class DateTimeForm(forms.Form):
-            dt = DateTimeField(initial=lambda: now, disabled=True)
+            dt = DateTimeField(initial=FakeTime().now, disabled=disabled)
 
-        form = DateTimeForm({})
+        return DateTimeForm({})
+
+    def test_datetime_clean_disabled_callable_initial_microseconds(self):
+        """
+        Cleaning a form with a disabled DateTimeField and callable initial
+        removes microseconds.
+        """
+        form = self.get_datetime_form_with_callable_initial(
+            disabled=True, microseconds=123456,
+        )
         self.assertEqual(form.errors, {})
-        self.assertEqual(form.cleaned_data, {'dt': now})
+        self.assertEqual(form.cleaned_data, {
+            'dt': datetime.datetime(2006, 10, 25, 14, 30, 46),
+        })
+
+    def test_datetime_clean_disabled_callable_initial_bound_field(self):
+        """
+        The cleaned value for a form with a disabled DateTimeField and callable
+        initial matches the bound field's cached initial value.
+        """
+        form = self.get_datetime_form_with_callable_initial(disabled=True)
+        self.assertEqual(form.errors, {})
+        cleaned = form.cleaned_data['dt']
+        self.assertEqual(cleaned, datetime.datetime(2006, 10, 25, 14, 30, 46))
+        bf = form['dt']
+        self.assertEqual(cleaned, bf.initial)
 
     def test_datetime_changed_data_callable_with_microseconds(self):
         class DateTimeForm(forms.Form):