To both use a Sequential object and access a layer with a string, you could also do something like this:
Method 1
class Model(nn.Module):
    def __init__(self, channels):
        super(Model, self).__init__()

        self.conv_block = nn.Sequential()
        for k in range(len(channels)-1):
            self.conv_block.add_module(
                'conv{}'.format(k),
                nn.Conv2d(channels[k], channels[k+1], kernel_size=3, padding=0)
            )

As it defines attributes, you could access the layer using the __getattr__ function:
channels = [1, 2, 4, 8]
model = Model(channels)
model.conv_block.__getattr__('conv1')

To get the list of all names, you could either extract them from dir(model.conv_block) (by selecting the ‘conv{}’, ‘linear{}’ etc.) or create an attribute self.layer_names.
Edit: Actually this may be cleaner (I realize this is very similar to a previous answer using ModuleDict). We can easily extend it when you have several blocks:
Method 2
class Model(nn.Module):

    def __init__(self, channels):
        super(Model, self).__init__()

        self.conv_block = nn.Sequential()
        self.layer2id = dict()
        self._layer_idx = self._conv_idx = 0

        for k in range(len(channels)-1):
            self.layer2id['conv{}'.format(self._conv_idx)] = self._layer_idx
            self.conv_block.add_module(
                'conv{}'.format(self._conv_idx),
                nn.Conv2d(channels[k], channels[k+1], kernel_size=3, padding=0)
            )
            self._layer_idx += 1
            self._conv_idx += 1

    def get_layer(self, name):
        return self.conv_block[self.layer2id[name]]

channels = [1, 2, 4, 8]
model = Model(channels)
model.get_layer('conv1')