diff --git a/pint/registry.py b/pint/registry.py
index 0129ba5..71e45a8 100644
--- a/pint/registry.py
+++ b/pint/registry.py
@@ -32,6 +32,7 @@
 
 from __future__ import division, unicode_literals, print_function, absolute_import
 
+import copy
 import os
 import re
 import math
@@ -47,7 +48,8 @@ from tokenize import NUMBER, NAME
 
 from . import registry_helpers
 from .context import Context, ContextChain
-from .util import (logger, pi_theorem, solve_dependencies, ParserHelper,
+from .util import (getattr_maybe_raise,
+                   logger, pi_theorem, solve_dependencies, ParserHelper,
                    string_preprocessor, find_connected_nodes,
                    find_shortest_path, UnitsContainer, _is_dim,
                    to_units_container, SourceIterator)
@@ -115,15 +117,7 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
     def __init__(self, filename='', force_ndarray=False, on_redefinition='warn', auto_reduce_dimensions=False):
 
         self._register_parsers()
-
-        from .unit import build_unit_class
-        self.Unit = build_unit_class(self)
-
-        from .quantity import build_quantity_class
-        self.Quantity = build_quantity_class(self, force_ndarray)
-
-        from .measurement import build_measurement_class
-        self.Measurement = build_measurement_class(self, force_ndarray)
+        self._init_dynamic_classes(force_ndarray)
 
         self._filename = filename
 
@@ -168,6 +162,18 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
 
         self._initialized = False
 
+    def _init_dynamic_classes(self, force_ndarray):
+        """Generate subclasses on the fly and attach them to self
+        """
+        from .unit import build_unit_class
+        self.Unit = build_unit_class(self)
+
+        from .quantity import build_quantity_class
+        self.Quantity = build_quantity_class(self, force_ndarray)
+
+        from .measurement import build_measurement_class
+        self.Measurement = build_measurement_class(self, force_ndarray)
+
     def _after_init(self):
         """This should be called after all __init__
         """
@@ -194,9 +200,15 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
             k, v = part.split('=')
             self._defaults[k.strip()] = v.strip()
 
+    def __deepcopy__(self, memo):
+        new = object.__new__(type(self))
+        new.__dict__ = copy.deepcopy(self.__dict__, memo)
+        force_ndarray = self.Quantity.force_ndarray
+        new._init_dynamic_classes(force_ndarray)
+        return new
+
     def __getattr__(self, item):
-        if item[0] == '_':
-            return super(BaseRegistry, self).__getattribute__(item)
+        getattr_maybe_raise(self, item)
         return self.Unit(item)
 
     def __getitem__(self, item):
@@ -1283,12 +1295,14 @@ class SystemRegistry(BaseRegistry):
         #: Map group name to group.
         #: :type: dict[ str | Group]
         self._groups = {}
-        self.Group = systems.build_group_class(self)
         self._groups['root'] = self.Group('root')
-        self.System = systems.build_system_class(self)
-
         self._default_system = system
 
+    def _init_dynamic_classes(self, force_ndarray):
+        super(SystemRegistry, self)._init_dynamic_classes(force_ndarray)
+        self.Group = systems.build_group_class(self)
+        self.System = systems.build_system_class(self)
+
     def _after_init(self):
         """After init function
 
diff --git a/pint/systems.py b/pint/systems.py
index 47b1ed1..e73428e 100644
--- a/pint/systems.py
+++ b/pint/systems.py
@@ -15,7 +15,7 @@ import re
 
 from .definitions import Definition, UnitDefinition
 from .errors import DefinitionSyntaxError, RedefinitionError
-from .util import to_units_container, SharedRegistryObject, SourceIterator, logger
+from .util import to_units_container, getattr_maybe_raise, SharedRegistryObject, SourceIterator, logger
 from .babel_names import _babel_systems
 from pint.compat import Loc
 
@@ -232,6 +232,7 @@ class _Group(SharedRegistryObject):
         return grp
 
     def __getattr__(self, item):
+        getattr_maybe_raise(self, item)
         return self._REGISTRY
 
 
@@ -297,6 +298,7 @@ class _System(SharedRegistryObject):
         return list(self.members)
 
     def __getattr__(self, item):
+        getattr_maybe_raise(self, item)
         u = getattr(self._REGISTRY, self.name + '_' + item, None)
         if u is not None:
             return u
@@ -440,6 +442,7 @@ class Lister(object):
         return list(self.d.keys())
 
     def __getattr__(self, item):
+        getattr_maybe_raise(self, item)
         return self.d[item]
 
 
diff --git a/pint/unit.py b/pint/unit.py
index adb871c..79f3048 100644
--- a/pint/unit.py
+++ b/pint/unit.py
@@ -64,7 +64,7 @@ class _Unit(PrettyIPython, SharedRegistryObject):
         return ret
 
     def __deepcopy__(self, memo):
-      ret = self.__class__(copy.deepcopy(self._units))
+      ret = self.__class__(copy.deepcopy(self._units, memo))
       ret.__used = self.__used
       return ret
 
diff --git a/pint/util.py b/pint/util.py
index 13fc149..587517a 100644
--- a/pint/util.py
+++ b/pint/util.py
@@ -243,6 +243,9 @@ class udict(dict):
     def __missing__(self, key):
         return 0.
 
+    def copy(self):
+        return udict(self)
+
 
 class UnitsContainer(Mapping):
     """The UnitsContainer stores the product of units and their respective
@@ -264,7 +267,7 @@ class UnitsContainer(Mapping):
                 raise TypeError('value must be a number, not {}'.format(type(value)))
             if not isinstance(value, float):
                 d[key] = float(value)
-        self._hash = hash(frozenset(self._d.items()))
+        self._hash = None
 
     def copy(self):
         return self.__copy__()
@@ -275,24 +278,28 @@ class UnitsContainer(Mapping):
         if newval:
             new._d[key] = newval
         else:
-            del new._d[key]
-
+            new._d.pop(key, None)
+        new._hash = None
         return new
 
     def remove(self, keys):
         """ Create a new UnitsContainer purged from given keys.
 
         """
-        d = udict(self._d)
-        return UnitsContainer(((key, d[key]) for key in d if key not in keys))
+        new = self.copy()
+        for k in keys:
+            new._d.pop(k, None)
+        new._hash = None
+        return new
 
     def rename(self, oldkey, newkey):
         """ Create a new UnitsContainer in which an entry has been renamed.
 
         """
-        d = udict(self._d)
-        d[newkey] = d.pop(oldkey)
-        return UnitsContainer(d)
+        new = self.copy()
+        new._d[newkey] = new._d.pop(oldkey)
+        new._hash = None
+        return new
 
     def __iter__(self):
         return iter(self._d)
@@ -307,19 +314,22 @@ class UnitsContainer(Mapping):
         return key in self._d
 
     def __hash__(self):
+        if self._hash is None:
+            self._hash = hash(frozenset(self._d.items()))
         return self._hash
 
+    # Only needed by Python 2.7
     def __getstate__(self):
-        return {'_d': self._d, '_hash': self._hash}
+        return self._d, self._hash
 
     def __setstate__(self, state):
-        self._d = state['_d']
-        self._hash = state['_hash']
+        self._d, self._hash = state
 
     def __eq__(self, other):
         if isinstance(other, UnitsContainer):
-            other = other._d
-        elif isinstance(other, string_types):
+            # Not the same as hash(self); see ParserHelper.__hash__ and __eq__
+            return UnitsContainer.__hash__(self) == UnitsContainer.__hash__(other)
+        if isinstance(other, string_types):
             other = ParserHelper.from_string(other)
             other = other._d
 
@@ -340,20 +350,25 @@ class UnitsContainer(Mapping):
         return format_unit(self, spec, **kwspec)
 
     def __copy__(self):
-        return UnitsContainer(self._d)
+        # Skip expensive health checks performed by __init__
+        out = object.__new__(self.__class__)
+        out._d = self._d.copy()
+        out._hash = self._hash
+        return out
 
     def __mul__(self, other):
-        d = udict(self._d)
         if not isinstance(other, self.__class__):
             err = 'Cannot multiply UnitsContainer by {}'
             raise TypeError(err.format(type(other)))
+
+        new = self.copy()
         for key, value in other.items():
-            d[key] += value
-        keys = [key for key, value in d.items() if value == 0]
-        for key in keys:
-            del d[key]
+            new._d[key] += value
+            if new._d[key] == 0:
+                del new._d[key]
 
-        return UnitsContainer(d)
+        new._hash = None
+        return new
 
     __rmul__ = __mul__
 
@@ -361,26 +376,26 @@ class UnitsContainer(Mapping):
         if not isinstance(other, NUMERIC_TYPES):
             err = 'Cannot power UnitsContainer by {}'
             raise TypeError(err.format(type(other)))
-        d = udict(self._d)
-        for key, value in d.items():
-            d[key] *= other
-        return UnitsContainer(d)
+
+        new = self.copy()
+        for key, value in new._d.items():
+            new._d[key] *= other
+        new._hash = None
+        return new
 
     def __truediv__(self, other):
         if not isinstance(other, self.__class__):
             err = 'Cannot divide UnitsContainer by {}'
             raise TypeError(err.format(type(other)))
 
-        d = udict(self._d)
-
+        new = self.copy()
         for key, value in other.items():
-            d[key] -= value
+            new._d[key] -= value
+            if new._d[key] == 0:
+                del new._d[key]
 
-        keys = [key for key, value in d.items() if value == 0]
-        for key in keys:
-            del d[key]
-
-        return UnitsContainer(d)
+        new._hash = None
+        return new
 
     def __rtruediv__(self, other):
         if not isinstance(other, self.__class__) and other != 1:
@@ -468,7 +483,9 @@ class ParserHelper(UnitsContainer):
                                  for key, value in ret.items()))
 
     def __copy__(self):
-        return ParserHelper(scale=self.scale, **self)
+        new = super(ParserHelper, self).__copy__()
+        new.scale = self.scale
+        return new
 
     def copy(self):
         return self.__copy__()
@@ -477,7 +494,14 @@ class ParserHelper(UnitsContainer):
         if self.scale != 1.0:
             mess = 'Only scale 1.0 ParserHelper instance should be considered hashable'
             raise ValueError(mess)
-        return self._hash
+        return super(ParserHelper, self).__hash__()
+
+    # Only needed by Python 2.7
+    def __getstate__(self):
+        return self._d, self._hash, self.scale
+
+    def __setstate__(self, state):
+        self._d, self._hash, self.scale = state
 
     def __eq__(self, other):
         if isinstance(other, self.__class__):
@@ -696,6 +720,16 @@ def fix_str_conversions(cls):
     return cls
 
 
+def getattr_maybe_raise(self, item):
+    """Helper function to invoke at the beginning of all overridden ``__getattr__``
+    methods. Raise AttributeError if the user tries to ask for a _ or __ attribute.
+    """
+    # Double-underscore attributes are tricky to detect because they are
+    # automatically prefixed with the class name - which may be a subclass of self
+    if item.startswith('_') or item.endswith('__'):
+        raise AttributeError("%r object has no attribute %r" % (self, item))
+
+
 class SourceIterator(object):
     """Iterator to facilitate reading the definition files.
 

deepcopy(ureg) broken
As of pint 0.9:
```python
import pint
import copy
ureg = pint.UnitRegistry()
copy.deepcopy(ureg)
```
Output:
```
lib/python3.7/copy.py in _deepcopy_dict(x, memo, deepcopy)
    238     memo[id(x)] = y
    239     for key, value in x.items():
--> 240         y[deepcopy(key, memo)] = deepcopy(value, memo)
    241     return y
    242 d[dict] = _deepcopy_dict

lib/python3.7/site-packages/pint/util.py in __hash__(self)
    475 
    476     def __hash__(self):
--> 477         if self.scale != 1.0:
    478             mess = 'Only scale 1.0 ParserHelper instance should be considered hashable'
    479             raise ValueError(mess)

AttributeError: scale
```
```python
>>> c = pint.util.ParserHelper(scale=1)
>>> c2 = copy.deepcopy(c)
>>> c2.scale
AttributeError: scale
```
diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index 6e79601..6ca7035 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -679,3 +679,23 @@ class TestIssues(QuantityTestCase):
     def test_issue783(self):
         ureg = UnitRegistry()
         assert not ureg('g') == []
+
+    def test_issue856(self):
+        ph1 = ParserHelper(scale=123)
+        ph2 = copy.deepcopy(ph1)
+        assert ph2.scale == ph1.scale
+
+        ureg1 = UnitRegistry()
+        ureg2 = copy.deepcopy(ureg1)
+        # Very basic functionality test
+        assert ureg2('1 t').to('kg').magnitude == 1000
+
+    def test_issue856b(self):
+        # Test that, after a deepcopy(), the two UnitRegistries are
+        # independent from each other
+        ureg1 = UnitRegistry()
+        ureg2 = copy.deepcopy(ureg1)
+        ureg1.define('test123 = 123 kg')
+        ureg2.define('test123 = 456 kg')
+        assert ureg1('1 test123').to('kg').magnitude == 123
+        assert ureg2('1 test123').to('kg').magnitude == 456

pip install -e .[dev]
parse_log_pytest
pytest
pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
packaging @ file:///croot/packaging_1734472117206/work
-e git+https://github.com/hgrecco/pint.git@55058bc88fe783693680be2344110592406a0722#egg=Pint
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work

name: pint
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
  - exceptiongroup=1.2.0=py39h06a4308_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=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=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
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
prefix: /opt/conda/envs/pint

pint/testsuite/test_issues.py::TestIssues::test_issue856 pint/testsuite/test_issues.py::TestIssues::test_issue856b
pint/testsuite/test_issues.py::TestIssues::test_alternative_angstrom_definition pint/testsuite/test_issues.py::TestIssues::test_angstrom_creation pint/testsuite/test_issues.py::TestIssues::test_issue104 pint/testsuite/test_issues.py::TestIssues::test_issue105 pint/testsuite/test_issues.py::TestIssues::test_issue121 pint/testsuite/test_issues.py::TestIssues::test_issue170 pint/testsuite/test_issues.py::TestIssues::test_issue252 pint/testsuite/test_issues.py::TestIssues::test_issue29 pint/testsuite/test_issues.py::TestIssues::test_issue323 pint/testsuite/test_issues.py::TestIssues::test_issue339 pint/testsuite/test_issues.py::TestIssues::test_issue354_356_370 pint/testsuite/test_issues.py::TestIssues::test_issue45 pint/testsuite/test_issues.py::TestIssues::test_issue468 pint/testsuite/test_issues.py::TestIssues::test_issue50 pint/testsuite/test_issues.py::TestIssues::test_issue52 pint/testsuite/test_issues.py::TestIssues::test_issue523 pint/testsuite/test_issues.py::TestIssues::test_issue532 pint/testsuite/test_issues.py::TestIssues::test_issue54 pint/testsuite/test_issues.py::TestIssues::test_issue54_related pint/testsuite/test_issues.py::TestIssues::test_issue61 pint/testsuite/test_issues.py::TestIssues::test_issue61_notNP pint/testsuite/test_issues.py::TestIssues::test_issue62 pint/testsuite/test_issues.py::TestIssues::test_issue625a pint/testsuite/test_issues.py::TestIssues::test_issue625b pint/testsuite/test_issues.py::TestIssues::test_issue625c pint/testsuite/test_issues.py::TestIssues::test_issue655a pint/testsuite/test_issues.py::TestIssues::test_issue655b pint/testsuite/test_issues.py::TestIssues::test_issue66 pint/testsuite/test_issues.py::TestIssues::test_issue66b pint/testsuite/test_issues.py::TestIssues::test_issue69 pint/testsuite/test_issues.py::TestIssues::test_issue783 pint/testsuite/test_issues.py::TestIssues::test_issue85 pint/testsuite/test_issues.py::TestIssues::test_issue86 pint/testsuite/test_issues.py::TestIssues::test_issue93 pint/testsuite/test_issues.py::TestIssues::test_issues86b pint/testsuite/test_issues.py::TestIssues::test_micro_creation
pint/util.py pint/registry.py pint/systems.py pint/unit.py
pint/util.py pint/registry.py pint/systems.py pint/unit.py