Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 40 additions & 22 deletions lib/iris/fileformats/cf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""

from abc import ABCMeta, abstractmethod
from abc import ABCMeta, abstractclassmethod

from collections.abc import Iterable, MutableMapping
import os
Expand Down Expand Up @@ -70,23 +70,34 @@ def _is_str_dtype(var):

################################################################################
class CFVariable(metaclass=ABCMeta):
"""Abstract base class wrapper for a CF-netCDF variable."""
"""
Abstract base class wrapper for a CF-netCDF variable.

Each object of this sort contains a netcdf variable object, and "wraps" it
so that its nc-related properties are accessible as properties of *this*
object, e.g. "cf_var.dimensions".

This class is abstract : various concrete subclasses encode the different
cf roles that a file variable may have.

"""

#: Name of the netCDF variable attribute that identifies this
#: CF-netCDF variable.
cf_identity = None

def __init__(self, name, data):
def __init__(self, name, nc_variable):
# Accessing the list of netCDF attributes is surprisingly slow.
# Since it's used repeatedly, caching the list makes things
# quite a bit faster.
self._nc_attrs = data.ncattrs()
# Get all names: see also __getitem__, which will cache their values.
self._nc_attrs = nc_variable.ncattrs()

#: NetCDF variable name.
self.cf_name = name

#: NetCDF4 Variable data instance.
self.cf_data = data
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the term 'data' is desperately confusing.
This is actually always a netcdf file variable, netCDF4.Variable (and not the variable's data).
Hence the rename !

self.nc_variable = nc_variable

#: Collection of CF-netCDF variables associated with this variable.
self.cf_group = None
Expand Down Expand Up @@ -115,8 +126,8 @@ def _identify_common(variables, ignore, target):

return (ignore, target)

@abstractmethod
def identify(self, variables, ignore=None, target=None, warn=True):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to match the signature in the concrete classes.

@abstractclassmethod
def identify(cls, variables, ignore=None, target=None, warn=True):
"""
Identify all variables that match the criterion for this CF-netCDF variable class.

Expand Down Expand Up @@ -173,26 +184,33 @@ def __hash__(self):
return hash(self.cf_name)

def __getattr__(self, name):
# Accessing netCDF attributes is surprisingly slow. Since
# they're often read repeatedly, caching the values makes things
# quite a bit faster.
# Extend the "wrapper" object by adding all the additional properties
# of the "wrapped" nc_variable.
value = getattr(self.nc_variable, name)

# Maintain the record of the "used" (cf-recognised) attributes.
if name in self._nc_attrs:
self._cf_attrs.add(name)
value = getattr(self.cf_data, name)

# Also *cache* these properties, on the wrapper object.
# Accessing netCDF attributes is surprisingly slow, as they're often
# read repeatedly. This makes things quite a bit faster.
setattr(self, name, value)
return value

# NOTE: getitem and len need to be provided *explicitly*, i.e. not via
# wrapping, for "magic" syntaxes to work : "len(x)" and "x[k]".
def __getitem__(self, key):
return self.cf_data.__getitem__(key)
return self.nc_variable.__getitem__(key)

def __len__(self):
return self.cf_data.__len__()
return self.nc_variable.__len__()

def __repr__(self):
return "%s(%r, %r)" % (
self.__class__.__name__,
self.cf_name,
self.cf_data,
self.nc_variable,
)

def cf_attrs(self):
Expand Down Expand Up @@ -557,8 +575,8 @@ class _CFFormulaTermsVariable(CFVariable):

cf_identity = "formula_terms"

def __init__(self, name, data, formula_root, formula_term):
CFVariable.__init__(self, name, data)
def __init__(self, name, nc_variable, formula_root, formula_term):
CFVariable.__init__(self, name, nc_variable)
# Register the formula root and term relationship.
self.add_formula_term(formula_root, formula_term)

Expand Down Expand Up @@ -607,7 +625,7 @@ def __repr__(self):
return "%s(%r, %r, %r)" % (
self.__class__.__name__,
self.cf_name,
self.cf_data,
self.nc_variable,
self.cf_terms_by_root,
)

Expand Down Expand Up @@ -832,8 +850,8 @@ class CFMeasureVariable(CFVariable):

cf_identity = "cell_measures"

def __init__(self, name, data, measure):
CFVariable.__init__(self, name, data)
def __init__(self, name, nc_variable, measure):
CFVariable.__init__(self, name, nc_variable)
#: Associated cell measure of the cell variable
self.cf_measure = measure

Expand Down Expand Up @@ -1092,7 +1110,7 @@ def _translate(self):
cf_name = cf_var.cf_name
if cf_var.cf_name not in self.cf_group:
self.cf_group[cf_name] = CFAuxiliaryCoordinateVariable(
cf_name, cf_var.cf_data
cf_name, cf_var.nc_variable
)
self.cf_group[cf_name].add_formula_term(cf_root, cf_term)

Expand Down Expand Up @@ -1232,7 +1250,7 @@ def _build(cf_variable):
cf_term in terms
and cf_var_name not in self.cf_group.promoted
):
data_var = CFDataVariable(cf_var_name, cf_var.cf_data)
data_var = CFDataVariable(cf_var_name, cf_var.nc_variable)
self.cf_group.promoted[cf_var_name] = data_var
_build(data_var)
break
Expand All @@ -1246,7 +1264,7 @@ def _build(cf_variable):
and cf_name not in self.cf_group.promoted
):
data_var = CFDataVariable(
cf_name, self.cf_group[cf_name].cf_data
cf_name, self.cf_group[cf_name].nc_variable
)
self.cf_group.promoted[cf_name] = data_var
_build(data_var)
Expand Down
4 changes: 2 additions & 2 deletions lib/iris/fileformats/netcdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ def _get_cf_var_data(cf_var, filename):

# Create cube with deferred data, but no metadata
fill_value = getattr(
cf_var.cf_data,
cf_var.nc_variable,
"_FillValue",
netCDF4.default_fillvals[cf_var.dtype.str[1:]],
)
Expand All @@ -568,7 +568,7 @@ def _get_cf_var_data(cf_var, filename):
)
# Get the chunking specified for the variable : this is either a shape, or
# maybe the string "contiguous".
chunks = cf_var.cf_data.chunking()
chunks = cf_var.nc_variable.chunking()
# In the "contiguous" case, pass chunks=None to 'as_lazy_data'.
if chunks == "contiguous":
chunks = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def setUp(self):
self.expected_chunks = _optimum_chunksize(self.shape, self.shape)

def _make(self, chunksizes):
cf_data = mock.Mock(_FillValue=None)
cf_data.chunking = mock.MagicMock(return_value=chunksizes)
nc_variable = mock.Mock(_FillValue=None)
nc_variable.chunking = mock.MagicMock(return_value=chunksizes)
cf_var = mock.MagicMock(
spec=iris.fileformats.cf.CFVariable,
dtype=np.dtype("i4"),
cf_data=cf_data,
nc_variable=nc_variable,
cf_name="DUMMY_VAR",
shape=self.shape,
)
Expand Down
12 changes: 6 additions & 6 deletions lib/iris/tests/unit/fileformats/netcdf/test__load_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ def _make(self, names, attrs):
cf_group[name] = mock.Mock(cf_attrs_unused=cf_attrs_unused)
cf = mock.Mock(cf_group=cf_group)

cf_data = mock.Mock(_FillValue=None)
cf_data.chunking = mock.MagicMock(return_value=shape)
nc_variable = mock.Mock(_FillValue=None)
nc_variable.chunking = mock.MagicMock(return_value=shape)
cf_var = mock.MagicMock(
spec=iris.fileformats.cf.CFVariable,
dtype=np.dtype("i4"),
cf_data=cf_data,
nc_variable=nc_variable,
cf_name="DUMMY_VAR",
cf_group=coords,
shape=shape,
Expand Down Expand Up @@ -129,12 +129,12 @@ def setUp(self):
def _make(self, attrs):
shape = (1,)
cf_attrs_unused = mock.Mock(return_value=attrs)
cf_data = mock.Mock(_FillValue=None)
cf_data.chunking = mock.MagicMock(return_value=shape)
nc_variable = mock.Mock(_FillValue=None)
nc_variable.chunking = mock.MagicMock(return_value=shape)
cf_var = mock.MagicMock(
spec=iris.fileformats.cf.CFVariable,
dtype=np.dtype("i4"),
cf_data=cf_data,
nc_variable=nc_variable,
cf_name="DUMMY_VAR",
cf_group=mock.Mock(),
cf_attrs_unused=cf_attrs_unused,
Expand Down