Yes, you are right that some modifications would be needed, in case you depend on the (missing) .features attribute.
The torchvision implementation can be found here 1 and you’ll see that the layers (or blocks) are called directly instead of using a features/classifier split.
You could create a custom model by reusing the torchvision.models.resnet18 and overriding the forward method.
Here is an example how to do it:
class MyResNet18(nn.Module):
    def __init__(self, resnet):
        super().__init__()
        # create features branch using https://github.com/pytorch/vision/blob/2a52c2dca73513d0d0c3e2a505aed05e5b9aa792/torchvision/models/resnet.py#L230-L246
        self.features = nn.Sequential(
            resnet.conv1,
            resnet.bn1,
            resnet.relu,
            resnet.maxpool,
            resnet.layer1,
            resnet.layer2,
            resnet.layer3,
            resnet.layer4
        )
        self.avgpool = resnet.avgpool
        self.fc = resnet.fc
        
    def _forward_impl(self, x: torch.Tensor) -> torch.Tensor:
        # See note [TorchScript super()]
        x = self.features(x)

        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)

        return x

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self._forward_impl(x)
    

# create standard model and reuse in custom one
model = models.resnet18()
print(model)
custom_model = MyResNet18(model)        

# check outputs
x = torch.randn(2, 3, 224, 224)
out = model(x)
custom_out = custom_model(x)

# compare outputs to make sure the model works as intended
print((out - custom_out).abs().max())
> tensor(0., grad_fn=<MaxBackward1>)

print(custom_model.features)