diff --git a/xarray/backends/netCDF4_.py b/xarray/backends/netCDF4_.py index b5c3413e7f8..93f01524e48 100644 --- a/xarray/backends/netCDF4_.py +++ b/xarray/backends/netCDF4_.py @@ -409,9 +409,16 @@ def ds(self): return self._acquire() def open_store_variable(self, name, var): + import netCDF4 + dimensions = var.dimensions data = indexing.LazilyIndexedArray(NetCDF4ArrayWrapper(name, self)) attributes = {k: var.getncattr(k) for k in var.ncattrs()} + enum_meaning = None + enum_name = None + if isinstance(var.datatype, netCDF4.EnumType): + enum_meaning = var.datatype.enum_dict + enum_name = var.datatype.name _ensure_fill_value_valid(data, attributes) # netCDF4 specific encoding; save _FillValue for later encoding = {} @@ -434,8 +441,9 @@ def open_store_variable(self, name, var): encoding["source"] = self._filename encoding["original_shape"] = var.shape encoding["dtype"] = var.dtype - - return Variable(dimensions, data, attributes, encoding) + return Variable(dimensions, data, attributes, encoding, + enum_meaning= enum_meaning, + enum_name=enum_name) def get_variables(self): return FrozenDict( @@ -478,7 +486,7 @@ def encode_variable(self, variable): return variable def prepare_variable( - self, name, variable, check_encoding=False, unlimited_dims=None + self, name, variable: Variable, check_encoding=False, unlimited_dims=None ): _ensure_no_forward_slash_in_name(name) @@ -503,12 +511,19 @@ def prepare_variable( variable, raise_on_invalid=check_encoding, unlimited_dims=unlimited_dims ) + enum = None + if variable.enum_meaning: + enum = self.ds.createEnumType( + variable.dtype, + variable.enum_name, + variable.enum_meaning) + if name in self.ds.variables: nc4_var = self.ds.variables[name] else: nc4_var = self.ds.createVariable( varname=name, - datatype=datatype, + datatype=enum if enum else datatype, dimensions=variable.dims, zlib=encoding.get("zlib", False), complevel=encoding.get("complevel", 4), diff --git a/xarray/backends/store.py b/xarray/backends/store.py index a507ee37470..2cbbe777637 100644 --- a/xarray/backends/store.py +++ b/xarray/backends/store.py @@ -28,7 +28,7 @@ def guess_can_open( def open_dataset( # type: ignore[override] # allow LSP violation, not supporting **kwargs self, - filename_or_obj: str | os.PathLike[Any] | BufferedIOBase | AbstractDataStore, + filename_or_obj: AbstractDataStore, *, mask_and_scale=True, decode_times=True, diff --git a/xarray/coding/strings.py b/xarray/coding/strings.py index d0bfb1a7a63..d498375f9a9 100644 --- a/xarray/coding/strings.py +++ b/xarray/coding/strings.py @@ -10,8 +10,7 @@ lazy_elemwise_func, pop_to, safe_setitem, - unpack_for_decoding, - unpack_for_encoding, + unpack, ) from xarray.core import indexing from xarray.core.parallelcompat import get_chunked_array_type, is_chunked_array @@ -48,7 +47,7 @@ def __init__(self, allows_unicode=True): self.allows_unicode = allows_unicode def encode(self, variable, name=None): - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) contains_unicode = is_unicode_dtype(data.dtype) encode_as_char = encoding.get("dtype") == "S1" @@ -69,17 +68,17 @@ def encode(self, variable, name=None): # TODO: figure out how to handle this in a lazy way with dask data = encode_string_array(data, string_encoding) - return Variable(dims, data, attrs, encoding) + return Variable(dims, data, attrs, encoding, enum_meaning=enum_meaning, enum_name=enum_name) def decode(self, variable, name=None): - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) if "_Encoding" in attrs: string_encoding = pop_to(attrs, encoding, "_Encoding") func = partial(decode_bytes_array, encoding=string_encoding) data = lazy_elemwise_func(data, func, np.dtype(object)) - return Variable(dims, data, attrs, encoding) + return Variable(dims, data, attrs, encoding, enum_meaning=enum_meaning, enum_name=enum_name) def decode_bytes_array(bytes_array, encoding="utf-8"): @@ -97,11 +96,11 @@ def encode_string_array(string_array, encoding="utf-8"): def ensure_fixed_length_bytes(var): """Ensure that a variable with vlen bytes is converted to fixed width.""" - dims, data, attrs, encoding = unpack_for_encoding(var) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(var) if check_vlen_dtype(data.dtype) == bytes: # TODO: figure out how to handle this with dask data = np.asarray(data, dtype=np.string_) - return Variable(dims, data, attrs, encoding) + return Variable(dims, data, attrs, encoding, enum_meaning=enum_meaning, enum_name=enum_name) class CharacterArrayCoder(VariableCoder): @@ -110,7 +109,7 @@ class CharacterArrayCoder(VariableCoder): def encode(self, variable, name=None): variable = ensure_fixed_length_bytes(variable) - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) if data.dtype.kind == "S" and encoding.get("dtype") is not str: data = bytes_to_char(data) if "char_dim_name" in encoding.keys(): @@ -118,16 +117,16 @@ def encode(self, variable, name=None): else: char_dim_name = f"string{data.shape[-1]}" dims = dims + (char_dim_name,) - return Variable(dims, data, attrs, encoding) + return Variable(dims, data, attrs, encoding, enum_meaning=enum_meaning, enum_name=enum_name) def decode(self, variable, name=None): - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) if data.dtype == "S1" and dims: encoding["char_dim_name"] = dims[-1] dims = dims[:-1] data = char_to_bytes(data) - return Variable(dims, data, attrs, encoding) + return Variable(dims, data, attrs, encoding, enum_meaning=enum_meaning, enum_name=enum_name) def bytes_to_char(arr): diff --git a/xarray/coding/times.py b/xarray/coding/times.py index 3745d61acc0..455eda122a8 100644 --- a/xarray/coding/times.py +++ b/xarray/coding/times.py @@ -17,8 +17,7 @@ lazy_elemwise_func, pop_to, safe_setitem, - unpack_for_decoding, - unpack_for_encoding, + unpack, ) from xarray.core import indexing from xarray.core.common import contains_cftime_datetimes, is_np_datetime_like @@ -702,7 +701,7 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: if np.issubdtype( variable.data.dtype, np.datetime64 ) or contains_cftime_datetimes(variable): - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) (data, units, calendar) = encode_cf_datetime( data, encoding.pop("units", None), encoding.pop("calendar", None) @@ -710,14 +709,14 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: safe_setitem(attrs, "units", units, name=name) safe_setitem(attrs, "calendar", calendar, name=name) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable def decode(self, variable: Variable, name: T_Name = None) -> Variable: units = variable.attrs.get("units", None) if isinstance(units, str) and "since" in units: - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) units = pop_to(attrs, encoding, "units") calendar = pop_to(attrs, encoding, "calendar") @@ -730,7 +729,7 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable: ) data = lazy_elemwise_func(data, transform, dtype) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -738,25 +737,25 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable: class CFTimedeltaCoder(VariableCoder): def encode(self, variable: Variable, name: T_Name = None) -> Variable: if np.issubdtype(variable.data.dtype, np.timedelta64): - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) data, units = encode_cf_timedelta(data, encoding.pop("units", None)) safe_setitem(attrs, "units", units, name=name) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable def decode(self, variable: Variable, name: T_Name = None) -> Variable: units = variable.attrs.get("units", None) if isinstance(units, str) and units in TIME_UNITS: - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) units = pop_to(attrs, encoding, "units") transform = partial(decode_cf_timedelta, units=units) dtype = np.dtype("timedelta64[ns]") data = lazy_elemwise_func(data, transform, dtype=dtype) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py index 8ba7dcbb0e2..4bc3ee586f1 100644 --- a/xarray/coding/variables.py +++ b/xarray/coding/variables.py @@ -167,13 +167,8 @@ def lazy_elemwise_func(array, func: Callable, dtype: np.typing.DTypeLike): return _ElementwiseFunctionArray(array, func, dtype) -def unpack_for_encoding(var: Variable) -> T_VarTuple: - return var.dims, var.data, var.attrs.copy(), var.encoding.copy() - - -def unpack_for_decoding(var: Variable) -> T_VarTuple: - return var.dims, var._data, var.attrs.copy(), var.encoding.copy() - +def unpack(var: Variable) -> T_VarTuple: + return var.dims, var.data, var.attrs.copy(), var.encoding.copy(), var.enum_meaning, var.enum_name, def safe_setitem(dest, key: Hashable, value, name: T_Name = None): if key in dest: @@ -219,7 +214,7 @@ class CFMaskCoder(VariableCoder): """Mask or unmask fill values according to CF conventions.""" def encode(self, variable: Variable, name: T_Name = None): - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) dtype = np.dtype(encoding.get("dtype", data.dtype)) fv = encoding.get("_FillValue") @@ -250,10 +245,10 @@ def encode(self, variable: Variable, name: T_Name = None): if not pd.isnull(fill_value) and not fv_exists: data = duck_array_ops.fillna(data, fill_value) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) def decode(self, variable: Variable, name: T_Name = None): - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) raw_fill_values = [ pop_to(attrs, encoding, attr, name=name) @@ -286,7 +281,7 @@ def decode(self, variable: Variable, name: T_Name = None): ) data = lazy_elemwise_func(data, transform, dtype) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -327,7 +322,7 @@ class CFScaleOffsetCoder(VariableCoder): """ def encode(self, variable: Variable, name: T_Name = None) -> Variable: - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) if "scale_factor" in encoding or "add_offset" in encoding: dtype = _choose_float_dtype(data.dtype, "add_offset" in encoding) @@ -337,12 +332,12 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: if "scale_factor" in encoding: data /= pop_to(encoding, attrs, "scale_factor", name=name) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) def decode(self, variable: Variable, name: T_Name = None) -> Variable: _attrs = variable.attrs if "scale_factor" in _attrs or "add_offset" in _attrs: - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) scale_factor = pop_to(attrs, encoding, "scale_factor", name=name) add_offset = pop_to(attrs, encoding, "add_offset", name=name) @@ -359,7 +354,7 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable: ) data = lazy_elemwise_func(data, transform, dtype) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -371,7 +366,7 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: # "_Unsigned = "true" to indicate that # integer data should be treated as unsigned" if variable.encoding.get("_Unsigned", "false") == "true": - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) pop_to(encoding, attrs, "_Unsigned") signed_dtype = np.dtype(f"i{data.dtype.itemsize}") @@ -380,13 +375,13 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: attrs["_FillValue"] = new_fill data = duck_array_ops.astype(duck_array_ops.around(data), signed_dtype) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable def decode(self, variable: Variable, name: T_Name = None) -> Variable: if "_Unsigned" in variable.attrs: - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) unsigned = pop_to(attrs, encoding, "_Unsigned") @@ -414,7 +409,7 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable: stacklevel=3, ) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -423,7 +418,7 @@ class DefaultFillvalueCoder(VariableCoder): """Encode default _FillValue if needed.""" def encode(self, variable: Variable, name: T_Name = None) -> Variable: - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) # make NaN the fill value for float types if ( "_FillValue" not in attrs @@ -431,7 +426,7 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: and np.issubdtype(variable.dtype, np.floating) ): attrs["_FillValue"] = variable.dtype.type(np.nan) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -448,22 +443,22 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: and ("dtype" not in variable.encoding) and ("dtype" not in variable.attrs) ): - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) attrs["dtype"] = "bool" data = duck_array_ops.astype(data, dtype="i1", copy=True) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable def decode(self, variable: Variable, name: T_Name = None) -> Variable: if variable.attrs.get("dtype", False) == "bool": - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) # overwrite (!) dtype in encoding, and remove from attrs # needed for correct subsequent encoding encoding["dtype"] = attrs.pop("dtype") data = BoolTypeArray(data) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -475,10 +470,10 @@ def encode(self): raise NotImplementedError() def decode(self, variable: Variable, name: T_Name = None) -> Variable: - dims, data, attrs, encoding = unpack_for_decoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) if not data.dtype.isnative: data = NativeEndiannessArray(data) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable @@ -491,7 +486,7 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: "S1", str, ): - dims, data, attrs, encoding = unpack_for_encoding(variable) + dims, data, attrs, encoding, enum_meaning, enum_name = unpack(variable) dtype = np.dtype(encoding.pop("dtype")) if dtype != variable.dtype: if np.issubdtype(dtype, np.integer): @@ -509,7 +504,7 @@ def encode(self, variable: Variable, name: T_Name = None) -> Variable: ) data = np.around(data) data = data.astype(dtype=dtype) - return Variable(dims, data, attrs, encoding, fastpath=True) + return Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) else: return variable diff --git a/xarray/conventions.py b/xarray/conventions.py index 5a6675d60c1..bce24c561ee 100644 --- a/xarray/conventions.py +++ b/xarray/conventions.py @@ -48,10 +48,6 @@ T_DatasetOrAbstractstore = Union[Dataset, AbstractDataStore] -def _var_as_tuple(var: Variable) -> T_VarTuple: - return var.dims, var.data, var.attrs.copy(), var.encoding.copy() - - def _infer_dtype(array, name: T_Name = None) -> np.dtype: """Given an object array with no missing values, infer its dtype from its first element @@ -106,7 +102,7 @@ def _copy_with_dtype(data, dtype: np.typing.DTypeLike): def ensure_dtype_not_object(var: Variable, name: T_Name = None) -> Variable: # TODO: move this from conventions to backends? (it's not CF related) if var.dtype.kind == "O": - dims, data, attrs, encoding = _var_as_tuple(var) + dims, data, attrs, encoding, enum_meaning, enum_name = variables.unpack(var) # leave vlen dtypes unchanged if strings.check_vlen_dtype(data.dtype) is not None: @@ -149,7 +145,7 @@ def ensure_dtype_not_object(var: Variable, name: T_Name = None) -> Variable: data = _copy_with_dtype(data, dtype=_infer_dtype(data, name)) assert data.dtype.kind != "O" or data.dtype.metadata - var = Variable(dims, data, attrs, encoding, fastpath=True) + var = Variable(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) return var @@ -286,14 +282,14 @@ def decode_cf_variable( var = variables.BooleanCoder().decode(var) - dimensions, data, attributes, encoding = variables.unpack_for_decoding(var) + dimensions, data, attributes, encoding, enum_meaning, enum_name = variables.unpack(var) encoding.setdefault("dtype", original_dtype) if not is_duck_dask_array(data): data = indexing.LazilyIndexedArray(data) - return Variable(dimensions, data, attributes, encoding=encoding, fastpath=True) + return Variable(dimensions, data, attributes, encoding=encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) def _update_bounds_attributes(variables: T_Variables) -> None: @@ -431,7 +427,7 @@ def stackable(dim: Hashable) -> bool: new_vars[k] = decode_cf_variable( k, v, - concat_characters=concat_characters, + concat_characters=False if v.enum_meaning else concat_characters, mask_and_scale=mask_and_scale, decode_times=decode_times, stack_char_dim=stack_char_dim, @@ -449,7 +445,6 @@ def stackable(dim: Hashable) -> bool: new_vars[k].encoding["coordinates"] = coord_str del var_attrs["coordinates"] coord_names.update(var_coord_names) - if decode_coords == "all": for attr_name in CF_RELATED_DATA: if attr_name in var_attrs: diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 5a68fc7ffac..3698e0f5b76 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -653,6 +653,10 @@ def to_dataset( return result + @property + def enum_meaning(self) -> dict | None: + return self.variable.enum_meaning + @property def name(self) -> Hashable | None: """The name of this array.""" diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index f1a0cb9dc34..c44494fa8a9 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -649,6 +649,7 @@ class Dataset( _close: Callable[[], None] | None _indexes: dict[Hashable, Index] _variables: dict[Hashable, Variable] + _enums: dict[int, str] __slots__ = ( "_attrs", @@ -669,6 +670,7 @@ def __init__( data_vars: Mapping[Any, Any] | None = None, coords: Mapping[Any, Any] | None = None, attrs: Mapping[Any, Any] | None = None, + _enums: dict[int, str] | None = None, ) -> None: if data_vars is None: data_vars = {} diff --git a/xarray/core/variable.py b/xarray/core/variable.py index c89545c43ae..bb877c8ddc4 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -333,9 +333,17 @@ class Variable(AbstractArray, NdimSizeLenMixin, VariableArithmetic): they can use more complete metadata in context of coordinate labels. """ - __slots__ = ("_dims", "_data", "_attrs", "_encoding") + __slots__ = ("_dims", "_data", "_attrs", "_encoding", "_enum_name", "_enum_meaning") - def __init__(self, dims, data, attrs=None, encoding=None, fastpath=False): + def __init__(self, + dims, + data, + attrs=None, + encoding=None, + enum_meaning: dict[str, int] | None = None, + enum_name: str | None = None, + fastpath=False, + ): """ Parameters ---------- @@ -354,6 +362,10 @@ def __init__(self, dims, data, attrs=None, encoding=None, fastpath=False): include '_FillValue', 'scale_factor', 'add_offset' and 'dtype'. Well-behaved code to serialize a Variable should ignore unrecognized encoding items. + enum_meaning: dict[str, int] | None, optional + todo + enum_name: str | None, optional + todo """ self._data = as_compatible_data(data, fastpath=fastpath) self._dims = self._parse_dimensions(dims) @@ -363,6 +375,16 @@ def __init__(self, dims, data, attrs=None, encoding=None, fastpath=False): self.attrs = attrs if encoding is not None: self.encoding = encoding + self._enum_name = enum_name + self._enum_meaning = enum_meaning + + @property + def enum_meaning(self) -> dict | None: + return self._enum_meaning + + @property + def enum_name(self) -> dict | None: + return self._enum_name @property def dtype(self) -> np.dtype: @@ -1094,6 +1116,8 @@ def _replace( data=_default, attrs=_default, encoding=_default, + enum_meaning: dict = _default, + enum_name: str = _default, ) -> T_Variable: if dims is _default: dims = copy.copy(self._dims) @@ -1103,7 +1127,11 @@ def _replace( attrs = copy.copy(self._attrs) if encoding is _default: encoding = copy.copy(self._encoding) - return type(self)(dims, data, attrs, encoding, fastpath=True) + if enum_meaning is _default: + enum_meaning = copy.copy(self._enum_meaning) + if enum_name is _default: + enum_name = copy.copy(self._enum_name) + return type(self)(dims, data, attrs, encoding, fastpath=True, enum_meaning=enum_meaning, enum_name=enum_name) def __copy__(self: T_Variable) -> T_Variable: return self._copy(deep=False)