diff --git a/CHANGES.rst b/CHANGES.rst
index 48b6cb4..2e16543 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,13 @@
 4.5.2 (unreleased)
 ------------------
 
+- Fix slicing of ``PersistentList`` to always return instances of the
+  same class. It was broken on Python 3 prior to 3.7.4.
+
+- Fix copying  of ``PersistentList`` and ``PersistentMapping`` using
+  ``copy.copy`` to also copy the underlying data object. This was
+  broken prior to Python 3.7.4.
+
 - Fix ``PersistentList`` to mark itself as changed after calling
   ``clear``. See `PR 115 <https://github.com/zopefoundation/persistent/pull/115/>`_.
 
diff --git a/persistent/list.py b/persistent/list.py
index cd68c93..f5a2d03 100644
--- a/persistent/list.py
+++ b/persistent/list.py
@@ -31,10 +31,13 @@ class PersistentList(UserList, persistent.Persistent):
     as changed and automatically persisted.
 
     .. versionchanged:: 4.5.2
-       Using the `clear` method, or writing ``del inst[:]`` now only
-       results in marking the instance as changed if it actually removed
+       Using the `clear` method, or deleting a slice (e.g., ``del inst[:]`` or ``del inst[x:x]``)
+       now only results in marking the instance as changed if it actually removed
        items.
+    .. versionchanged:: 4.5.2
+       The `copy` method is available on Python 2.
     """
+    __super_getitem = UserList.__getitem__
     __super_setitem = UserList.__setitem__
     __super_delitem = UserList.__delitem__
     __super_iadd = UserList.__iadd__
@@ -49,43 +52,62 @@ class PersistentList(UserList, persistent.Persistent):
     __super_clear = (
         UserList.clear
         if hasattr(UserList, 'clear')
-        else lambda inst: inst.__delitem__(slice(None, None, None))
+        else lambda inst: inst.__delitem__(_SLICE_ALL)
     )
 
+    if not PYTHON2 and sys.version_info[:3] < (3, 7, 4):
+        # Prior to 3.7.4, Python 3 (but not Python 2) failed to properly
+        # return an instance of the same class.
+        # See https://bugs.python.org/issue27639
+        # and https://github.com/zopefoundation/persistent/issues/112.
+        # We only define the special method on the necessary versions to avoid
+        # any speed penalty.
+        def __getitem__(self, item):
+            result = self.__super_getitem(item)
+            if isinstance(item, slice):
+                result = self.__class__(result)
+            return result
+
+    if sys.version_info[:3] < (3, 7, 4):
+        # Likewise for __copy__, except even Python 2 needs it.
+        # See https://github.com/python/cpython/commit/3645d29a1dc2102fdb0f5f0c0129ff2295bcd768
+        def __copy__(self):
+            inst = self.__class__.__new__(self.__class__)
+            inst.__dict__.update(self.__dict__)
+            # Create a copy and avoid triggering descriptors
+            inst.__dict__["data"] = self.__dict__["data"][:]
+            return inst
+
     def __setitem__(self, i, item):
         self.__super_setitem(i, item)
         self._p_changed = 1
 
     def __delitem__(self, i):
         # If they write del list[:] but we're empty,
-        # no need to mark us changed.
-        needs_changed = i != _SLICE_ALL or bool(self)
+        # no need to mark us changed. Likewise with
+        # a slice that's empty, like list[1:1].
+        len_before = len(self.data)
         self.__super_delitem(i)
-        if needs_changed:
+        if len(self.data) != len_before:
             self._p_changed = 1
 
     if PYTHON2:  # pragma: no cover
         __super_setslice = UserList.__setslice__
         __super_delslice = UserList.__delslice__
 
+        def copy(self):
+            return self.__class__(self)
+
         def __setslice__(self, i, j, other):
             self.__super_setslice(i, j, other)
             self._p_changed = 1
 
         def __delslice__(self, i, j):
-            # On Python 2, the slice dunders, like ``__delslice__``,
-            # are documented as getting ``(0, sys.maxsize)`` as the
-            # arguments for list[:]. Prior to 2.7.10, it was
-            # incorrectly documented as ``(0, sys.maxint)``, but that
-            # usually worked because ``sys.maxsize`` and
-            # ``sys.maxint`` are usually the same. But on 64-bit
-            # Windown, where ``sizeof(int) == sizeof(long) == 4``,
-            # they're different (``sys.maxsize`` is bigger). See
-            # https://bugs.python.org/issue23645
-            needs_changed = i == 0 and j == sys.maxsize and bool(self)
-            self.__super_delslice(i, j)
-            if needs_changed:
-                self._p_changed = 1
+            # In the past we just called super, but we want to apply the
+            # same _p_changed optimization logic that __delitem__ does. Don't
+            # call it as ``self.__delitem__``, though, because user code in subclasses
+            # on Python 2 may not be expecting to get a slice.
+            PersistentList.__delitem__(self, slice(i, j))
 
     def __iadd__(self, other):
         L = self.__super_iadd(other)
diff --git a/persistent/mapping.py b/persistent/mapping.py
index 60b0b03..57f61fa 100644
--- a/persistent/mapping.py
+++ b/persistent/mapping.py
@@ -16,6 +16,8 @@
 
 $Id$"""
 
+import sys
+
 import persistent
 from persistent._compat import IterableUserDict
 
@@ -55,6 +57,25 @@ class PersistentMapping(IterableUserDict, persistent.Persistent):
     __super_pop = IterableUserDict.pop
     __super_popitem = IterableUserDict.popitem
 
+
+    # Be sure to make a deep copy of our ``data`` (See PersistentList.)
+    # See https://github.com/python/cpython/commit/3645d29a1dc2102fdb0f5f0c0129ff2295bcd768
+    # This was fixed in CPython 3.7.4, but we can't rely on that because it
+    # doesn't handle our old ``_container`` appropriately (it goes directly
+    # to ``self.__dict__``, bypassing the descriptor). The code here was initially
+    # based on the version found in 3.7.4.
+    def __copy__(self):
+        inst = self.__class__.__new__(self.__class__)
+        inst.__dict__.update(self.__dict__)
+        # Create a copy and avoid triggering descriptors
+        if '_container' in inst.__dict__:
+            # BWC for ZODB < 3.3.
+            data = inst.__dict__.pop('_container')
+        else:
+            data = inst.__dict__['data']
+        inst.__dict__["data"] = data.copy()
+        return inst
+
     def __delitem__(self, key):
         self.__super_delitem(key)
         self._p_changed = 1
@@ -121,8 +142,10 @@ class PersistentMapping(IterableUserDict, persistent.Persistent):
         self._p_changed = 1
         return result
 
-    # Old implementations used _container rather than data.
-    # Use a descriptor to provide data when we have _container instead
+    # Old implementations (prior to 2001; see
+    # https://github.com/zopefoundation/ZODB/commit/c64281cf2830b569eed4f211630a8a61d22a0f0b#diff-b0f568e20f51129c10a096abad27c64a)
+    # used ``_container`` rather than ``data``. Use a descriptor to provide
+    # ``data`` when we have ``_container`` instead
 
     @default
     def data(self): # pylint:disable=method-hidden

PersistentList slicing returns a non-persistent list on python3
Since `__setslice__()` and the likes have been killed in python3 (in favor of `__getitem__(slice(...)`), it seems that PersistentList must be updated, else slices return regular lists and the DB gets dangerously filled with non-persistent mutables:

```pycon
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from persistent.list import PersistentList
>>> a = PersistentList([1, 2,3])
>>> isinstance(a, PersistentList)
True
>>> isinstance(a[1:], PersistentList)
False
```
diff --git a/persistent/tests/test_list.py b/persistent/tests/test_list.py
index 34a6017..53bd74c 100644
--- a/persistent/tests/test_list.py
+++ b/persistent/tests/test_list.py
@@ -16,7 +16,10 @@
 
 import unittest
 
+
 from persistent.tests.utils import TrivialJar
+from persistent.tests.utils import copy_test
+
 
 l0 = []
 l1 = [0]
@@ -268,7 +271,8 @@ class TestPList(unittest.TestCase):
         self.assertEqual(inst, [1, 2, 3])
         self.assertTrue(inst._p_changed)
 
-    def test_delslice_nonempty(self):
+    def test_delslice_all_nonempty_list(self):
+        # Delete everything from a non-empty list
         inst = self._makeOne([1, 2, 3])
         self.assertFalse(inst._p_changed)
         self.assertEqual(inst, [1, 2, 3])
@@ -276,7 +280,23 @@ class TestPList(unittest.TestCase):
         self.assertEqual(inst, [])
         self.assertTrue(inst._p_changed)
 
-    def test_delslice_empty(self):
+    def test_delslice_sub_nonempty_list(self):
+        # delete a sub-list from a non-empty list
+        inst = self._makeOne([0, 1, 2, 3])
+        self.assertFalse(inst._p_changed)
+        del inst[1:2]
+        self.assertEqual(inst, [0, 2, 3])
+        self.assertTrue(inst._p_changed)
+
+    def test_delslice_empty_nonempty_list(self):
+        # delete an empty sub-list from a non-empty list
+        inst = self._makeOne([0, 1, 2, 3])
+        self.assertFalse(inst._p_changed)
+        del inst[1:1]
+        self.assertEqual(inst, [0, 1, 2, 3])
+        self.assertFalse(inst._p_changed)
+
+    def test_delslice_all_empty_list(self):
         inst = self._makeOne([])
         self.assertFalse(inst._p_changed)
         self.assertEqual(inst, [])
@@ -347,6 +367,33 @@ class TestPList(unittest.TestCase):
         self.assertEqual(inst, [1, 2])
         self.assertTrue(inst._p_changed)
 
+    def test_getslice_same_class(self):
+        class MyList(self._getTargetClass()):
+            pass
+
+        inst = MyList()
+        inst._p_jar = self._makeJar()
+        # Entire thing, empty.
+        inst2 = inst[:]
+        self.assertIsNot(inst, inst2)
+        self.assertEqual(inst, inst2)
+        self.assertIsInstance(inst2, MyList)
+        # The _p_jar is *not* propagated.
+        self.assertIsNotNone(inst._p_jar)
+        self.assertIsNone(inst2._p_jar)
+
+        # Partial
+        inst.extend((1, 2, 3))
+        inst2 = inst[1:2]
+        self.assertEqual(inst2, [2])
+        self.assertIsInstance(inst2, MyList)
+        self.assertIsNone(inst2._p_jar)
+
+    def test_copy(self):
+        inst = self._makeOne()
+        inst.append(42)
+        copy_test(self, inst)
+
 
 def test_suite():
     return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/persistent/tests/test_mapping.py b/persistent/tests/test_mapping.py
index d93fadc..f932790 100644
--- a/persistent/tests/test_mapping.py
+++ b/persistent/tests/test_mapping.py
@@ -13,7 +13,9 @@
 ##############################################################################
 import unittest
 
+
 from persistent.tests.utils import TrivialJar
+from persistent.tests.utils import copy_test
 
 # pylint:disable=blacklisted-name, protected-access
 
@@ -281,6 +283,25 @@ class PersistentMappingTests(unittest.TestCase):
         # and we are marked as changed.
         self.assertTrue(pm._p_changed)
 
+    def test_copy(self):
+        pm = self._makeOne()
+        pm['key'] = 42
+        copy = copy_test(self, pm)
+        self.assertEqual(42, copy['key'])
+
+    def test_copy_legacy_container(self):
+        pm = self._makeOne()
+        pm['key'] = 42
+        pm.__dict__['_container'] = pm.__dict__.pop('data')
+
+        self.assertNotIn('data', pm.__dict__)
+        self.assertIn('_container', pm.__dict__)
+
+        copy = copy_test(self, pm)
+        self.assertNotIn('_container', copy.__dict__)
+        self.assertIn('data', copy.__dict__)
+        self.assertEqual(42, copy['key'])
+
 
 class Test_legacy_PersistentDict(unittest.TestCase):
 
diff --git a/persistent/tests/utils.py b/persistent/tests/utils.py
index 320c546..76e21d7 100644
--- a/persistent/tests/utils.py
+++ b/persistent/tests/utils.py
@@ -71,3 +71,24 @@ class RememberingJar(object):
         # This isn't what setstate() is supposed to do,
         # but it suffices for the tests.
         obj.__setstate__(self.remembered)
+
+
+def copy_test(self, obj):
+    import copy
+    # Test copy.copy. Do this first, because depending on the
+    # version of Python, `UserDict.copy()` may wind up
+    # mutating the original object's ``data`` (due to our
+    # BWC with ``_container``). This shows up only as a failure
+    # of coverage.
+    obj.test = [1234]  # Make sure instance vars are also copied.
+    obj_copy = copy.copy(obj)
+    self.assertIsNot(obj.data, obj_copy.data)
+    self.assertEqual(obj.data, obj_copy.data)
+    self.assertIs(obj.test, obj_copy.test)
+
+    # Test internal copy
+    obj_copy = obj.copy()
+    self.assertIsNot(obj.data, obj_copy.data)
+    self.assertEqual(obj.data, obj_copy.data)
+
+    return obj_copy

pip install -e .[test]
parse_log_pytest
pytest
zope.testrunner manuel pytest
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
manuel==1.13.0
packaging @ file:///croot/packaging_1671697413597/work
-e git+https://github.com/zopefoundation/persistent.git@a6a18eaa8f347530646ce413f93f90a584ab2fb2#egg=persistent
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycparser==2.21
pytest==7.1.2
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
zipp @ file:///croot/zipp_1672387121353/work
zope.exceptions==5.1
zope.interface==6.4.post2
zope.testrunner==6.5

name: persistent
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
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_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=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cffi==1.15.1
      - manuel==1.13.0
      - pycparser==2.21
      - zope-exceptions==5.1
      - zope-interface==6.4.post2
      - zope-testrunner==6.5
prefix: /opt/conda/envs/persistent

persistent/tests/test_list.py::TestPList::test_delslice_empty_nonempty_list persistent/tests/test_mapping.py::PersistentMappingTests::test_copy_legacy_container
persistent/tests/test_list.py::TestPList::testTheWorld persistent/tests/test_list.py::TestPList::test_append persistent/tests/test_list.py::TestPList::test_clear_empty persistent/tests/test_list.py::TestPList::test_clear_nonempty persistent/tests/test_list.py::TestPList::test_copy persistent/tests/test_list.py::TestPList::test_delslice_all_empty_list persistent/tests/test_list.py::TestPList::test_delslice_all_nonempty_list persistent/tests/test_list.py::TestPList::test_delslice_sub_nonempty_list persistent/tests/test_list.py::TestPList::test_extend persistent/tests/test_list.py::TestPList::test_getslice_same_class persistent/tests/test_list.py::TestPList::test_iadd persistent/tests/test_list.py::TestPList::test_imul persistent/tests/test_list.py::TestPList::test_insert persistent/tests/test_list.py::TestPList::test_remove persistent/tests/test_list.py::TestPList::test_reverse persistent/tests/test_list.py::TestPList::test_setslice persistent/tests/test_list.py::TestPList::test_volatile_attributes_not_persisted persistent/tests/test_list.py::test_suite persistent/tests/test_mapping.py::Test_default::test___get___from_class persistent/tests/test_mapping.py::Test_default::test___get___from_instance persistent/tests/test_mapping.py::PersistentMappingTests::testTheWorld persistent/tests/test_mapping.py::PersistentMappingTests::test___repr___converts_legacy_container_attr persistent/tests/test_mapping.py::PersistentMappingTests::test_clear_empty persistent/tests/test_mapping.py::PersistentMappingTests::test_clear_empty_legacy_container persistent/tests/test_mapping.py::PersistentMappingTests::test_clear_nonempty persistent/tests/test_mapping.py::PersistentMappingTests::test_copy persistent/tests/test_mapping.py::PersistentMappingTests::test_update_keywords persistent/tests/test_mapping.py::PersistentMappingTests::test_volatile_attributes_not_persisted persistent/tests/test_mapping.py::Test_legacy_PersistentDict::test_PD_is_alias_to_PM persistent/tests/test_mapping.py::test_suite
persistent/mapping.py persistent/list.py CHANGES.rst
persistent/mapping.py persistent/list.py CHANGES.rst