Issue title: Unsettable attributes and magicgui LayerDataTuple are incompatible
 Issue body: ## 🐛 Bug

To update inplace a result layer with a `@magicgui` decorated function, one should return a `LayerDataTuple`. However, this breaks if any of the attributes passed in the dictionary are not settable, such as `ndim` for Points.

## To Reproduce

```python
import napari
from napari.types import LayerDataTuple
from napari.layers import Points
from magicgui import magicgui

@magicgui()
def func(points: Points) -> LayerDataTuple:
    return (), dict(name='result', ndim=3), 'points'

v = napari.Viewer()
v.add_points()
v.window.add_dock_widget(func)
func()
func()
```

This fails with
```python
~/git/napari/napari/utils/_magicgui.py in add_layer_data_tuples_to_viewer(gui, result, return_type)
    148                     layer.data = layer_datum[0]
    149                     for k, v in layer_datum[1].items():
--> 150                         setattr(layer, k, v)
    151                     continue
    152                 except KeyError:  # layer not in the viewer

AttributeError: can't set attribute
```

## Expected behavior

I'm not sure about the best approach, but I feel like there should be no failure when the attribute (even if not settable) is already correct. In this case, ndim was 3, so setting it to 3 should just be a noop.

## Environment

```yaml
napari: 0.4.9rc1.dev12+gbf03a0d4
Platform: Linux-5.12.14-arch1-1-x86_64-with-glibc2.33
System: Arch Linux
Python: 3.9.6 (default, Jun 30 2021, 10:22:16) [GCC 11.1.0]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.20.3
SciPy: 1.7.0
Dask: 2020.12.0
VisPy: 0.6.5.dev716+g91430ea4.d20210629

OpenGL:
- GL version: 4.6.0 NVIDIA 465.31
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1920x1080, scale 1.0
- screen 2: resolution 1920x1200, scale 1.0

Plugins:
- animation: 0.0.2
- console: 0.0.3
- mrcfile-reader: 0.1.2
- napari-em-reader: 0.1.0
- napari-properties-plotter: 0.2.0
- ndtiffs: 0.1.1
- scikit-image: 0.4.9
- svg: 0.1.5
```
 Comments: 
 Comment 0: I'm not sure I agree here.  It's an AttributeError to try to set an unsettable attribute with a regular layer too (`layer.ndim` is a [computed property](https://github.com/napari/napari/blob/7b6a9a5e75f9efcf947b88dd9a6fb066dd775636/napari/layers/base/base.py#L725-L728) without a setter), so I'm not sure it makes sense to special case magicgui here.  

```python
In [6]: from napari.layers import Points

In [7]: p = Points()

In [8]: p.ndim
Out[8]: 2

In [9]: p.ndim = 2
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-1703bc7b9c53> in <module>
----> 1 p.ndim = 2

AttributeError: can't set attribute
```

may I ask why you were trying to set ndim in the first place?
 Comment 1: I'm creating a result layer that sometimes (e.g: at initialization) will have no data. If one passes no data and no ndim, it defaults to 2D. However, I needed it to be 3D, cause otherwise updating it fails later on.

This can obviously be worked around in a few ways (passing a (0, 3) array instead of None, passing a different tuple altogether, etc...), but I figured it was worth pointing out, in case other less-circumventable attributes fall in this category. I see now that it's probably just `ndim` which has this problem, so maybe this is more a matter of `should ndim be settable` :P
 Comment 2: oh right, I forgot that points takes an `ndim` parameter in the init.  Honestly, I feel like that's the issue here.  I would vote for using a different name in the initializer, like `init_ndim` ... if you tried to set ndim on an already existent numpy array you'd get the same attribute error.  So i feel like our convenience of creating an array if one isn't provided needs to have a different parameter naming
 Comment 3: That's reasonable! Might be worth considering in #2995 then, which was prompted by similar issues.
 Comment 4: > oh right, I forgot that points takes an ndim parameter in the init. Honestly, I feel like that's the issue here. I would vote for using a different name in the initializer, like init_ndim ... if you tried to set ndim on an already existent numpy array you'd get the same attribute error. So i feel like our convenience of creating an array if one isn't provided needs to have a different parameter naming

Yeah i agree with this take - we should somehow think about the init method. But even if one renamed `init_ndim` would that fix the issue here? If you put `init_ndim` in the dict you wouldn't be able to set it either, so maybe we just need to not have things in the init that aren't settable afterwards? But that seems maybe a little strict? Maybe i'm missing something here though and there is another approach.
 Comment 5: > But even if one renamed init_ndim would that fix the issue here?

good point.  no it wouldn't.  I guess Points is just a little unique in that regard in that one can't use the same parameters to update it as to create it.  @brisvag, a (crappy) workaround is to ask whether you've already called the function with `function.call_count`:

```python
@magicgui()
def func(points: Points) -> LayerDataTuple:
    meta = {'name': 'result3'}
    if not func.call_count:
        meta['ndim'] = 3
    return (), meta, 'points'
```

short of that, you'll need to ask for the viewer in your function parameters to check whether the layer exists

 Comment 6: >  I guess Points is just a little unique in that regard in that one can't use the same parameters to update it as to create it

We could drop `ndim` - you can pass `data=np.empty((0, 3))` instead, but that's a little awkward. We could overload `data=3` which is unambiguous, but might be a bit heavy. What does @jni think here?
 Comment 7: Or as @alisterburt suggested in #2995, we coukd make ndim settable, which (when possible) would pad the layer data promoting it to a higher dimension...
 Comment 8: > Or as @alisterburt suggested in #2995, we coukd make ndim settable, which (when possible) would pad the layer data promoting it to a higher dimension...

That's an interesting idea, we could pad or trim as needed. I'm open, but curious what others think. I wonder if we'd then do that for images etc too ...
 Comment 9: Checking back on this. What do we think about the padding proposal if higher `ndim` is passed, and error if lower is passed? cc @jni
 Comment 10: > Checking back on this. What do we think about the padding proposal if higher ndim is passed, and error if lower is passed? cc @jni

I guess this is relevant for Shapes, Vectors, and Points where we allow you to pass in a kwarg that then isn't settable later. I guess we could say that we should never do that. If you can pass an input then you should always be able to set it later. I'd probably trim rather than error. I think if we can do that consistently across all layer types and add some nice round trip tests then I'd be ok with it, but I'd rather not special case solve something here just for Points and leave us with inconsistencies across layer types.

I wonder what @andy-sweet makes of this from a design stand point and if he has any recommendations?
 Comment 11: Sorry, I'm catching up here and trying to be clear, so excuse any stating the obvious.

> I guess this is relevant for Shapes, Vectors, and Points where we allow you to pass in a kwarg that then isn't settable later. I guess we could say that we should never do that. If you can pass an input then you should always be able to set it later.

As a general design principle (i.e. not specific to napari), I think it's fine to have an init parameter that doesn't have a corresponding property setter. In my opinion it's preferred because (public) mutable state is one of the common enemies of good design.

For napari and its layers/models, there is often more of a one-to-one correspondence between init parameter and property setter. That partially comes from pydantic, but even there you can have immutable fields (i.e. where setting is not allowed). The problem here seems to come from mixing the somewhat special behavior of magicgui to modify an existing layer in-place and the state defined in `Layer._get_state` (which I interpret as mostly being for deserialization through `__init__`). If we had a clear indication that `ndim` is immutable (e.g. like a frozen field in pydantic) then skipping it in this case (and maybe checking it's compatibility with the instance being updated) seems fine to me - maybe the lack of a property setter is a strong enough indicator?

Renaming the init parameter seems to only partially solve the problem - I think you also have to decide what key/value gets stored in `_get_state`. There's also the question of how the user understands what key/values they can pass in the second element of the layer data tuple.

Coming back to the original issue, but if I ignore the behavior of the magicgui decorated function, I'd initially question why we'd want to allow `ndim` to mutate at all - at that point, I think you may as well just recreate the layer rather than deal with all the side effects that changing `ndim` will have. Like previously mentioned (if I understood correctly), you only need to specify `ndim` if it can't be inferred from the data alone - that seems like a perfect candidate for an initialization parameter that doesn't have a setter.
 Comment 12: > I think you also have to decide what key/value gets stored in _get_state

Good points @andy-sweet - one thing i'll add is that a big goal is to make the layer models actually be pydantic models and then drop `_get_state` and use pydantics built in serialization. I'd love to see this so so so much for the napari code base.

 Comment 13: > Good points @andy-sweet - one thing i'll add is that a big goal is to make the layer models actually be pydantic models and then drop `_get_state` and use pydantics built in serialization.

but this requires removing all information about the current render state (I know, I promise longer text about this, but writing takes a longer time than I expect).
 Comment 14: > but this requires removing all information about the current render state

the layer specific render state should be on the layer model. i'm not sure i understand this
{'source': 'GithubIssues', 'repo_name_with_owner': 'napari/napari', 'issue_id': 940595482, 'repo_stars': 1493.0}