def replace(self, *args, **kwargs):
        """
        replace(lower=None, upper=None, lower_inc=None, upper_inc=None)

        Returns a new instance of self with the given arguments replaced. It
        takes the exact same arguments as the constructor.

            >>> intrange(1, 5).replace(upper=10)
            intrange([1,10))
            >>> intrange(1, 10).replace(lower_inc=False)
            intrange([2,10))
            >>> intrange(1, 10).replace(5)
            intrange([5,10))

        Note that range objects are immutable and are never modified in place.
        """

        replacements = {
            "lower" : self.lower,
            "upper" : self.upper,
            "lower_inc" : self.lower_inc,
            "upper_inc" : self.upper_inc
        }
        replacements.update(
            dict(zip(("lower", "upper", "lower_inc", "upper_inc"), args)))
        replacements.update(kwargs)

        return self.__class__(**replacements)