The code below (following this tutorial) seems to do the trick. I might turn it into an addition to the Sage codebase at some point (it needs documentation and tests for that, at the very least), but I don't have the time. If you feel like doing that, be my guest!

Just save to product_ring.py and, in Sage do
sage: %runfile product_ring.py # from command line

or

load product_ring.py # from notebook.

```
import sage
from sage.rings.ring import Ring
from sage.rings.all import ZZ
from sage.structure.element import RingElement
from sage.categories.category import Category
from sage.structure.unique_representation import UniqueRepresentation
from sage.categories.rings import Rings
from sage.categories.pushout import ConstructionFunctor
class ProductRingElement(RingElement):
def __init__(self, data, parent=None):
if parent is None:
raise ValueError, "The parent must be provided"
self._data = tuple(data)
RingElement.__init__(self,parent)
def _repr_(self):
return "(" + ", ".join(x._repr_() for x in self._data) + ")"
def __cmp__(self, other):
return cmp(self._data, other._data)
def _add_(self, other):
C = self.__class__
z = zip(self._data, other._data)
return C(tuple(zz[0]._add_(zz[1]) for zz in z), parent=self.parent())
def _sub_(self, other):
C = self.__class__
z = zip(self._data, other._data)
return C(tuple(zz[0]._sub_(zz[1]) for zz in z), parent=self.parent())
def _mul_(self, other):
C = self.__class__
z = zip(self._data, other._data)
return C(tuple(zz[0]._mul_(zz[1]) for zz in z), parent=self.parent())
def _div_(self, other):
C = self.__class__
z = zip(self._data, other._data)
return C(tuple(zz[0]._div_(zz[1]) for zz in z), parent=self.parent())
def __iter__(self):
r"""
Returns an iterator of the entries of ``self``.
"""
for x in self._data:
yield x
class ProductRing(UniqueRepresentation, Ring):
r"""
The product ring of a finite number of rings, with elementwise addition
and multiplication.
"""
Element = ProductRingElement
def __init__(self,rings, base=ZZ, category=None):
r"""
INPUT:
- ``rings`` -- a tuple of rings.
"""
from sage.categories.rings import Rings
if not all(R.is_ring() for R in rings):
raise TypeError("Expected a tuple of rings as input.")
self._rings = tuple(rings)
Ring.__init__(self, base=base, category=category or Rings())
def _repr_(self):
return "Product ring: (" + ", ".join(R._repr_() for R in self._rings) + ")"
def _element_constructor_(self, *args, **kwds):
if len(args) == len(self._rings):
z = zip(self._rings, args)
elif len(args) == 1:
try:
if len(args[0]) != len(self._rings):
raise TypeError # also if args[0] has no len()
z = zip(self._rings, args[0])
except TypeError:
z = zip(self._rings, [args[0] for x in self._rings])
else:
raise TypeError("Wrong number of ring elements")
return self.element_class(tuple(zz[0](zz[1]) for zz in z), parent=self, **kwds)
def _coerce_map_from_(self, S):
if all(R.has_coerce_map_from(S) for R in self._rings):
return True
def __pow__(self,n):
r"""
Returns the ``n``-th power of self as a vector space.
"""
from sage.modules ...
```

(more)
Good question! I need this myself.