diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index 5629f46277..8deb9cfaa8 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -14,7 +14,9 @@ from django.db.models.fields.files import FileField, ImageField  # NOQA
 from django.db.models.fields.proxy import OrderWrt  # NOQA
 from django.db.models.lookups import Lookup, Transform  # NOQA
 from django.db.models.manager import Manager  # NOQA
-from django.db.models.query import Q, Prefetch, QuerySet  # NOQA
+from django.db.models.query import (  # NOQA
+    Q, Prefetch, QuerySet, prefetch_related_objects,
+)
 
 # Imports that would create circular imports if sorted
 from django.db.models.base import Model  # NOQA isort:skip
diff --git a/django/db/models/query.py b/django/db/models/query.py
index f37ed0f1b6..82074d700b 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -654,7 +654,7 @@ class QuerySet(object):
 
     def _prefetch_related_objects(self):
         # This method can only be called once the result cache has been filled.
-        prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
+        prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
         self._prefetch_done = True
 
     ##################################################
@@ -1368,15 +1368,12 @@ def normalize_prefetch_lookups(lookups, prefix=None):
     return ret
 
 
-def prefetch_related_objects(result_cache, related_lookups):
+def prefetch_related_objects(model_instances, *related_lookups):
     """
-    Helper function for prefetch_related functionality
-
-    Populates prefetched objects caches for a list of results
-    from a QuerySet
+    Populate prefetched object caches for a list of model instances based on
+    the lookups/Prefetch instances given.
     """
-
-    if len(result_cache) == 0:
+    if len(model_instances) == 0:
         return  # nothing to do
 
     related_lookups = normalize_prefetch_lookups(related_lookups)
@@ -1401,7 +1398,7 @@ def prefetch_related_objects(result_cache, related_lookups):
 
         # Top level, the list of objects to decorate is the result cache
         # from the primary QuerySet. It won't be for deeper levels.
-        obj_list = result_cache
+        obj_list = model_instances
 
         through_attrs = lookup.prefetch_through.split(LOOKUP_SEP)
         for level, through_attr in enumerate(through_attrs):
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 9892c9eb6e..f626391dec 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -920,6 +920,10 @@ results; these ``QuerySets`` are then used in the ``self.toppings.all()`` calls.
 The additional queries in ``prefetch_related()`` are executed after the
 ``QuerySet`` has begun to be evaluated and the primary query has been executed.
 
+If you have an iterable of model instances, you can prefetch related attributes
+on those instances using the :func:`~django.db.models.prefetch_related_objects`
+function.
+
 Note that the result cache of the primary ``QuerySet`` and all specified related
 objects will then be fully loaded into memory. This changes the typical
 behavior of ``QuerySets``, which normally try to avoid loading all objects into
@@ -2998,8 +3002,8 @@ by the aggregate.
 
 .. _SQLite documentation: https://www.sqlite.org/contrib
 
-Query-related classes
-=====================
+Query-related tools
+===================
 
 This section provides reference material for query-related tools not documented
 elsewhere.
@@ -3064,3 +3068,21 @@ attribute:
     provide a significant speed improvement over traditional
     ``prefetch_related`` calls which store the cached result within a
     ``QuerySet`` instance.
+
+``prefetch_related_objects()``
+------------------------------
+
+.. function:: prefetch_related_objects(model_instances, *related_lookups)
+
+.. versionadded:: 1.10
+
+Prefetches the given lookups on an iterable of model instances. This is useful
+in code that receives a list of model instances as opposed to a ``QuerySet``;
+for example, when fetching models from a cache or instantiating them manually.
+
+Pass an iterable of model instances (must all be of the same class) and the
+lookups or :class:`Prefetch` objects you want to prefetch for. For example::
+
+    >>> from django.db.models import prefetch_related_objects
+    >>> restaurants = fetch_top_restaurants_from_cache()  # A list of Restaurants
+    >>> prefetch_related_objects(restaurants, 'pizzas__toppings')
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index 1220d9402c..277e9d379a 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -312,6 +312,9 @@ Models
   app label and class interpolation using the ``'%(app_label)s'`` and
   ``'%(class)s'`` strings.
 
+* The :func:`~django.db.models.prefetch_related_objects` function is now a
+  public API.
+
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/tests/prefetch_related/test_prefetch_related_objects.py b/tests/prefetch_related/test_prefetch_related_objects.py
new file mode 100644
index 0000000000..db82fe88f7
--- /dev/null
+++ b/tests/prefetch_related/test_prefetch_related_objects.py
@@ -0,0 +1,119 @@
+from django.db.models import Prefetch, prefetch_related_objects
+from django.test import TestCase
+
+from .models import Author, Book, Reader
+
+
+class PrefetchRelatedObjectsTests(TestCase):
+    """
+    Since prefetch_related_objects() is just the inner part of
+    prefetch_related(), only do basic tests to ensure its API hasn't changed.
+    """
+    @classmethod
+    def setUpTestData(cls):
+        cls.book1 = Book.objects.create(title='Poems')
+        cls.book2 = Book.objects.create(title='Jane Eyre')
+        cls.book3 = Book.objects.create(title='Wuthering Heights')
+        cls.book4 = Book.objects.create(title='Sense and Sensibility')
+
+        cls.author1 = Author.objects.create(name='Charlotte', first_book=cls.book1)
+        cls.author2 = Author.objects.create(name='Anne', first_book=cls.book1)
+        cls.author3 = Author.objects.create(name='Emily', first_book=cls.book1)
+        cls.author4 = Author.objects.create(name='Jane', first_book=cls.book4)
+
+        cls.book1.authors.add(cls.author1, cls.author2, cls.author3)
+        cls.book2.authors.add(cls.author1)
+        cls.book3.authors.add(cls.author3)
+        cls.book4.authors.add(cls.author4)
+
+        cls.reader1 = Reader.objects.create(name='Amy')
+        cls.reader2 = Reader.objects.create(name='Belinda')
+
+        cls.reader1.books_read.add(cls.book1, cls.book4)
+        cls.reader2.books_read.add(cls.book2, cls.book4)
+
+    def test_unknown(self):
+        book1 = Book.objects.get(id=self.book1.id)
+        with self.assertRaises(AttributeError):
+            prefetch_related_objects([book1], 'unknown_attribute')
+
+    def test_m2m_forward(self):
+        book1 = Book.objects.get(id=self.book1.id)
+        with self.assertNumQueries(1):
+            prefetch_related_objects([book1], 'authors')
+
+        with self.assertNumQueries(0):
+            self.assertEqual(set(book1.authors.all()), {self.author1, self.author2, self.author3})
+
+    def test_m2m_reverse(self):
+        author1 = Author.objects.get(id=self.author1.id)
+        with self.assertNumQueries(1):
+            prefetch_related_objects([author1], 'books')
+
+        with self.assertNumQueries(0):
+            self.assertEqual(set(author1.books.all()), {self.book1, self.book2})
+
+    def test_foreignkey_forward(self):
+        authors = list(Author.objects.all())
+        with self.assertNumQueries(1):
+            prefetch_related_objects(authors, 'first_book')
+
+        with self.assertNumQueries(0):
+            [author.first_book for author in authors]
+
+    def test_foreignkey_reverse(self):
+        books = list(Book.objects.all())
+        with self.assertNumQueries(1):
+            prefetch_related_objects(books, 'first_time_authors')
+
+        with self.assertNumQueries(0):
+            [list(book.first_time_authors.all()) for book in books]
+
+    def test_m2m_then_m2m(self):
+        """
+        We can follow a m2m and another m2m.
+        """
+        authors = list(Author.objects.all())
+        with self.assertNumQueries(2):
+            prefetch_related_objects(authors, 'books__read_by')
+
+        with self.assertNumQueries(0):
+            self.assertEqual(
+                [
+                    [[str(r) for r in b.read_by.all()] for b in a.books.all()]
+                    for a in authors
+                ],
+                [
+                    [['Amy'], ['Belinda']],  # Charlotte - Poems, Jane Eyre
+                    [['Amy']],               # Anne - Poems
+                    [['Amy'], []],           # Emily - Poems, Wuthering Heights
+                    [['Amy', 'Belinda']],    # Jane - Sense and Sense
+                ]
+            )
+
+    def test_prefetch_object(self):
+        book1 = Book.objects.get(id=self.book1.id)
+        with self.assertNumQueries(1):
+            prefetch_related_objects([book1], Prefetch('authors'))
+
+        with self.assertNumQueries(0):
+            self.assertEqual(set(book1.authors.all()), {self.author1, self.author2, self.author3})
+
+    def test_prefetch_object_to_attr(self):
+        book1 = Book.objects.get(id=self.book1.id)
+        with self.assertNumQueries(1):
+            prefetch_related_objects([book1], Prefetch('authors', to_attr='the_authors'))
+
+        with self.assertNumQueries(0):
+            self.assertEqual(set(book1.the_authors), {self.author1, self.author2, self.author3})
+
+    def test_prefetch_queryset(self):
+        book1 = Book.objects.get(id=self.book1.id)
+        with self.assertNumQueries(1):
+            prefetch_related_objects(
+                [book1],
+                Prefetch('authors', queryset=Author.objects.filter(id__in=[self.author1.id, self.author2.id]))
+            )
+
+        with self.assertNumQueries(0):
+            self.assertEqual(set(book1.authors.all()), {self.author1, self.author2})