diff --git a/holoviews/core/__init__.py b/holoviews/core/__init__.py index 90df314d05..f24658a029 100644 --- a/holoviews/core/__init__.py +++ b/holoviews/core/__init__.py @@ -40,6 +40,7 @@ def public(obj): SheetCoordinateSystem, AttrTree] return any([issubclass(obj, bc) for bc in baseclasses]) -_public = list({_k for _k, _v in locals().items() if public(_v)}) -__all__ = _public + ["boundingregion", "dimension", "layer", "layout", - "ndmapping", "operation", "options", "sheetcoords", "tree", "element"] +__all__ = [ + *{_k for _k, _v in locals().items() if public(_v)}, + "boundingregion", "dimension", "layer", "layout", "ndmapping", "operation", "options", "sheetcoords", "tree", "element" +] diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index f74f05e9ee..278b41611b 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -56,9 +56,7 @@ def pipelined_call(*args, **kwargs): if isinstance(result, Dataset): result._pipeline = inst_pipeline.instance( - operations=inst_pipeline.operations + [ - init_op, call_op - ], + operations=[*inst_pipeline.operations, init_op, call_op], output_type=type(result), ) elif isinstance(result, MultiDimensionalMapping): @@ -69,9 +67,7 @@ def pipelined_call(*args, **kwargs): args=[key], ) element._pipeline = inst_pipeline.instance( - operations=inst_pipeline.operations + [ - init_op, call_op, getitem_op - ], + operations=[*inst_pipeline.operations, init_op, call_op, getitem_op], output_type=type(result), ) finally: @@ -431,7 +427,7 @@ def __call__(self, specs=None, **dimensions): if renames: data = obj.interface.redim(obj, renames) transform = self._create_expression_transform(kdims, vdims, list(renames.values())) - transforms = obj._transforms + [transform] + transforms = [*obj._transforms, transform] clone = obj.clone(data, kdims=kdims, vdims=vdims, transforms=transforms) if self._obj.dimensions(label='name') == clone.dimensions(label='name'): # Ensure that plot_id is inherited as long as dimension diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py index 157a709cf3..6587f668b4 100644 --- a/holoviews/core/data/__init__.py +++ b/holoviews/core/data/__init__.py @@ -206,7 +206,7 @@ def pipelined_fn(*args, **kwargs): if not in_method: if isinstance(result, Dataset): result._pipeline = inst_pipeline.instance( - operations=inst_pipeline.operations + [op], + operations=[*inst_pipeline.operations, op], output_type=type(result), ) @@ -219,9 +219,7 @@ def pipelined_fn(*args, **kwargs): args=[key] ) element._pipeline = inst_pipeline.instance( - operations=inst_pipeline.operations + [ - op, getitem_op - ], + operations=[*inst_pipeline.operations, op, getitem_op], output_type=type(result), ) finally: @@ -343,7 +341,7 @@ def __init__(self, data, kdims=None, vdims=None, **kwargs): kwargs=dict(kwargs, kdims=self.kdims, vdims=self.vdims), ) self._pipeline = input_pipeline.instance( - operations=input_pipeline.operations + [init_op], + operations=[*input_pipeline.operations, init_op], output_type=type(self), ) self._transforms = input_transforms or [] @@ -606,8 +604,8 @@ def select(self, selection_expr=None, selection_specs=None, **selection): if selection_specs is not None and not isinstance(selection_specs, (list, tuple)): selection_specs = [selection_specs] - selection = {dim_name: sel for dim_name, sel in selection.items() - if dim_name in self.dimensions()+['selection_mask']} + sel_dims = (*self.dimensions(), 'selection_mask') + selection = {dim: sel for dim, sel in selection.items() if dim in sel_dims} if (selection_specs and not any(self.matches(sp) for sp in selection_specs) or (not selection and not selection_expr)): return self @@ -811,7 +809,7 @@ def sample(self, samples=None, bounds=None, closest=True, **kwargs): reindexed = selection.clone(new_type=Dataset, datatype=datatype).reindex(kdims) selection = tuple(reindexed.columns(kdims+self.vdims).values()) - datatype = list(core_util.unique_iterator(self.datatype+['dataframe', 'dict'])) + datatype = list(core_util.unique_iterator([*self.datatype, 'dataframe', 'dict'])) return self.clone(selection, kdims=kdims, new_type=new_type, datatype=datatype) @@ -1185,7 +1183,7 @@ def clone(self, data=None, shared_data=True, new_type=None, link=True, Cloned object """ if 'datatype' not in overrides: - datatypes = [self.interface.datatype] + self.datatype + datatypes = [self.interface.datatype, *self.datatype] overrides['datatype'] = list(core_util.unique_iterator(datatypes)) if data is None: diff --git a/holoviews/core/data/array.py b/holoviews/core/data/array.py index af16e13fbf..364a74794c 100644 --- a/holoviews/core/data/array.py +++ b/holoviews/core/data/array.py @@ -247,7 +247,7 @@ def assign(cls, dataset, new_data): idx = dataset.get_dimension_index(d) data[:, idx] = arr new_cols = [arr for d, arr in new_data.items() if dataset.get_dimension(d) is None] - return np.column_stack([data]+new_cols) + return np.column_stack([data, *new_cols]) @classmethod diff --git a/holoviews/core/data/grid.py b/holoviews/core/data/grid.py index 1fb2e79109..71d389857d 100644 --- a/holoviews/core/data/grid.py +++ b/holoviews/core/data/grid.py @@ -122,7 +122,7 @@ def init(cls, eltype, data, kdims, vdims): if shape[-1] != len(vdims): raise error('The shape of the value array does not match the number of value dimensions.') shape = shape[:-1] - if (not expected and shape == (1,)) or (len(set((shape,)+shapes)) == 1 and len(shape) > 1): + if (not expected and shape == (1,)) or (len(shape) > 1 and len({shape, *shapes}) == 1): # If empty or an irregular mesh pass elif len(shape) != len(expected): diff --git a/holoviews/core/data/image.py b/holoviews/core/data/image.py index a5c1de1132..3834cb33b2 100644 --- a/holoviews/core/data/image.py +++ b/holoviews/core/data/image.py @@ -253,7 +253,7 @@ def sample(cls, dataset, samples=None): if len(samples[0]) == 1: select = {dataset.kdims[0].name: [s[0] for s in samples]} return tuple(dataset.select(**select).columns().values()) - return [c+(dataset.data[dataset._coord2matrix(c)],) for c in samples] + return [(*c, dataset.data[dataset._coord2matrix(c)]) for c in samples] @classmethod diff --git a/holoviews/core/data/interface.py b/holoviews/core/data/interface.py index d5d868d1ac..e1c03154c8 100644 --- a/holoviews/core/data/interface.py +++ b/holoviews/core/data/interface.py @@ -39,7 +39,7 @@ def __getitem__(self, index): args=[index], ) res._pipeline = self.dataset.pipeline.instance( - operations=self.dataset.pipeline.operations + [getitem_op], + operations=[*self.dataset.pipeline.operations, getitem_op], output_type=type(self.dataset) ) finally: @@ -91,7 +91,7 @@ def _perform_getitem(cls, dataset, index): kdims = [d for d in dims if d in kdims] vdims = [d for d in dims if d in vdims] - datatypes = util.unique_iterator([dataset.interface.datatype]+dataset.datatype) + datatypes = util.unique_iterator([dataset.interface.datatype, *dataset.datatype]) datatype = [dt for dt in datatypes if dt in Interface.interfaces and not Interface.interfaces[dt].gridded] if not datatype: datatype = ['dataframe', 'dictionary'] @@ -118,7 +118,7 @@ def _perform_getitem(cls, dataset, indices): params = {} if hasattr(ds, 'bounds'): params['bounds'] = None - return dataset.clone(selected, datatype=[ds.interface.datatype]+ds.datatype, **params) + return dataset.clone(selected, datatype=[ds.interface.datatype, *ds.datatype], **params) class Interface(param.Parameterized): diff --git a/holoviews/core/data/multipath.py b/holoviews/core/data/multipath.py index 6a1b7f0d30..354a8728bc 100644 --- a/holoviews/core/data/multipath.py +++ b/holoviews/core/data/multipath.py @@ -567,8 +567,8 @@ def ensure_ring(geom, values=None): values = geom breaks = np.where(np.isnan(geom.astype('float')).sum(axis=1))[0] - starts = [0] + list(breaks+1) - ends = list(breaks-1) + [len(geom)-1] + starts = [0, *(breaks + 1)] + ends = [*(breaks - 1), len(geom) - 1] zipped = zip(geom[starts], geom[ends], ends, values[starts]) unpacked = tuple(zip(*[(v, i+1) for s, e, i, v in zipped if (s!=e).any()])) diff --git a/holoviews/core/data/spatialpandas.py b/holoviews/core/data/spatialpandas.py index 6e62c5d7c9..c7c339ce9c 100644 --- a/holoviews/core/data/spatialpandas.py +++ b/holoviews/core/data/spatialpandas.py @@ -815,7 +815,7 @@ def to_spatialpandas(data, xdim, ydim, columns=None, geom='point'): converted['geometry'] = GeoSeries(geom_array) else: converted['geometry'] = GeoSeries(single_array([])) - return GeoDataFrame(converted, columns=['geometry']+columns) + return GeoDataFrame(converted, columns=['geometry', *columns]) def to_geom_dict(eltype, data, kdims, vdims, interface=None): diff --git a/holoviews/core/data/xarray.py b/holoviews/core/data/xarray.py index 9da2170c80..a9dc58a43a 100644 --- a/holoviews/core/data/xarray.py +++ b/holoviews/core/data/xarray.py @@ -267,7 +267,7 @@ def validate(cls, dataset, vdims=True): nonmatching = [f'{kd}: {dims}' for kd, dims in irregular[1:] if set(dims) != set(irregular[0][1])] if nonmatching: - nonmatching = ['{}: {}'.format(*irregular[0])] + nonmatching + nonmatching = ['{}: {}'.format(*irregular[0]), *nonmatching] raise DataError("The dimensions of coordinate arrays " "on irregular data must match. The " "following kdims were found to have " diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index fa05ac8e57..5fa322b851 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -1067,7 +1067,7 @@ def select(self, selection_specs=None, **kwargs): selection_specs = [selection_specs] # Apply all indexes applying on this object - vdims = self.vdims+['value'] if self.vdims else [] + vdims = [*self.vdims, 'value'] if self.vdims else [] kdims = self.kdims local_kwargs = {k: v for k, v in kwargs.items() if k in kdims+vdims} @@ -1106,14 +1106,14 @@ def select(self, selection_specs=None, **kwargs): return selection elif type(selection) is not type(self) and isinstance(selection, Dimensioned): # Apply the selection on the selected object of a different type - dimensions = selection.dimensions() + ['value'] + dimensions = [*selection.dimensions(), 'value'] if any(kw in dimensions for kw in kwargs): selection = selection.select(selection_specs=selection_specs, **kwargs) elif isinstance(selection, Dimensioned) and selection._deep_indexable: # Apply the deep selection on each item in local selection items = [] for k, v in selection.items(): - dimensions = v.dimensions() + ['value'] + dimensions = [*v.dimensions(), 'value'] if any(kw in dimensions for kw in kwargs): items.append((k, v.select(selection_specs=selection_specs, **kwargs))) else: @@ -1308,7 +1308,7 @@ class ViewableTree(AttrTree, Dimensioned): def __init__(self, items=None, identifier=None, parent=None, **kwargs): if items and all(isinstance(item, Dimensioned) for item in items): items = self._process_items(items) - params = {p: kwargs.pop(p) for p in list(self.param)+['id', 'plot_id'] if p in kwargs} + params = {p: kwargs.pop(p) for p in [*self.param, 'id', 'plot_id'] if p in kwargs} AttrTree.__init__(self, items, identifier, parent, **kwargs) Dimensioned.__init__(self, self.data, **params) @@ -1355,7 +1355,7 @@ def _deduplicate_items(cls, items): counts = defaultdict(lambda: 0) for path, item in items: if counter[path] > 1: - path = path + (util.int_to_roman(counts[path]+1),) + path = (*path, util.int_to_roman(counts[path] + 1)) else: inc = 1 while counts[path]: diff --git a/holoviews/core/layout.py b/holoviews/core/layout.py index 46a5137313..c3d68e8f6e 100644 --- a/holoviews/core/layout.py +++ b/holoviews/core/layout.py @@ -46,7 +46,7 @@ def __lshift__(self, other): if isinstance(other, (ViewableElement, NdMapping, Empty)): return AdjointLayout([self, other]) elif isinstance(other, AdjointLayout): - return AdjointLayout(other.data.values()+[self]) + return AdjointLayout([*other.data.values(), self]) else: raise TypeError(f'Cannot append {type(other).__name__} to a AdjointLayout') diff --git a/holoviews/core/operation.py b/holoviews/core/operation.py index ab0c4899b4..dfd448108b 100644 --- a/holoviews/core/operation.py +++ b/holoviews/core/operation.py @@ -155,9 +155,7 @@ def _apply(self, element, key=None): and isinstance(element, Dataset) and not in_method): ret._dataset = element.dataset.clone() ret._pipeline = element_pipeline.instance( - operations=element_pipeline.operations + [ - self.instance(**self.p) - ], + operations=[*element_pipeline.operations, self.instance(**self.p)], ) ret._transforms = element._transforms return ret diff --git a/holoviews/core/options.py b/holoviews/core/options.py index c42b4954ee..016ebd3df4 100644 --- a/holoviews/core/options.py +++ b/holoviews/core/options.py @@ -767,7 +767,7 @@ def closest(self, obj, group, defaults=True, backend=None): # Try to get a cache hit in the backend lookup cache backend = backend or Store.current_backend cache = Store._lookup_cache.get(backend, {}) - cache_key = opts_spec+(group, defaults, id(self.root)) + cache_key = (*opts_spec, group, defaults, id(self.root)) if cache_key in cache: return cache[cache_key] @@ -1344,7 +1344,7 @@ def add_style_opts(cls, component, new_options, backend=None): for option in new_options: if option not in cls.registry[backend][component].style_opts: plot_class = cls.registry[backend][component] - plot_class.style_opts = sorted(plot_class.style_opts+[option]) + plot_class.style_opts = sorted([*plot_class.style_opts, option]) cls._options[backend][component.name] = Options( 'style', merge_keywords=True, allowed_keywords=new_options ) @@ -1592,7 +1592,7 @@ def validate_spec(cls, spec, backends=None): error_key = (error.invalid_keyword, error.allowed_keywords.target, error.group_name) - error_info[error_key+(backend,)] = error.allowed_keywords + error_info[(*error_key, backend)] = error.allowed_keywords backend_errors[error_key].add(backend) for ((keyword, target, group_name), backend_error) in backend_errors.items(): diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py index f7fd786e42..cb18975ade 100644 --- a/holoviews/core/overlay.py +++ b/holoviews/core/overlay.py @@ -328,5 +328,7 @@ def decollate(self): return decollate(self) -__all__ = list({_k for _k, _v in locals().items() - if isinstance(_v, type) and issubclass(_v, Dimensioned)}) + ['Overlayable'] +__all__ = [ + *{_k for _k, _v in locals().items() if isinstance(_v, type) and issubclass(_v, Dimensioned)}, + "Overlayable" +] diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py index 2bce983830..7391ac97ed 100644 --- a/holoviews/core/spaces.py +++ b/holoviews/core/spaces.py @@ -297,7 +297,7 @@ def __lshift__(self, other): if isinstance(other, (ViewableElement, UniformNdMapping, Empty)): return AdjointLayout([self, other]) elif isinstance(other, AdjointLayout): - return AdjointLayout(other.data+[self]) + return AdjointLayout([*other.data, self]) else: raise TypeError(f'Cannot append {type(other).__name__} to a AdjointLayout') @@ -1048,7 +1048,7 @@ def clone(self, data=None, shared_data=True, new_type=None, link=True, overrides['plot_id'] = self._plot_id clone = super(UniformNdMapping, self).clone( callback, shared_data, new_type, link, - *(data,) + args, **overrides) + *(data, *args), **overrides) # Ensure the clone references this object to ensure # stream sources are inherited @@ -1747,7 +1747,7 @@ def __lshift__(self, other): if isinstance(other, (ViewableElement, UniformNdMapping)): return AdjointLayout([self, other]) elif isinstance(other, AdjointLayout): - return AdjointLayout(other.data+[self]) + return AdjointLayout([*other.data, self]) else: raise TypeError(f'Cannot append {type(other).__name__} to a AdjointLayout') diff --git a/holoviews/core/tree.py b/holoviews/core/tree.py index 339cd66a61..3e1ea39146 100644 --- a/holoviews/core/tree.py +++ b/holoviews/core/tree.py @@ -160,7 +160,7 @@ def _propagate(self, path, val): else: self.data[path] = val if self.parent is not None: - self.parent._propagate((self.identifier,)+path, val) + self.parent._propagate((self.identifier, *path), val) def __setitem__(self, identifier, val): diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 279d6dbbd5..97b87eec58 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -994,7 +994,7 @@ def max_range(ranges, combined=True): with warnings.catch_warnings(): warnings.filterwarnings('ignore', r'All-NaN (slice|axis) encountered') values = [tuple(np.nan if v is None else v for v in r) for r in ranges] - if any(isinstance(v, datetime_types) and not isinstance(v, cftime_types+(dt.time,)) + if any(isinstance(v, datetime_types) and not isinstance(v, (*cftime_types, dt.time)) for r in values for v in r): converted = [] for l, h in values: @@ -1281,7 +1281,7 @@ def dimension_sort(odict, kdims, vdims, key_index): indexes = [(dimensions[i], int(i not in range(ndims)), i if i in range(ndims) else i-ndims) for i in key_index] - cached_values = {d.name: [None]+list(d.values) for d in dimensions} + cached_values = {d.name: [None, *d.values] for d in dimensions} if len(set(key_index)) != len(key_index): raise ValueError("Cannot sort on duplicated dimensions") @@ -1434,8 +1434,8 @@ def get_overlay_spec(o, k, v): Gets the type.group.label + key spec from an Element in an Overlay. """ k = wrap_tuple(k) - return ((type(v).__name__, v.group, v.label) + k if len(o.kdims) else - (type(v).__name__,) + k) + return ((type(v).__name__, v.group, v.label, *k) if len(o.kdims) else + (type(v).__name__, *k)) def layer_sort(hmap): @@ -1887,9 +1887,9 @@ def make_path_unique(path, counts, new): path = path[:-1] else: added = True - path = path + (int_to_roman(count),) + path = (*path, int_to_roman(count)) if len(path) == 1: - path = path + (int_to_roman(counts.get(path, 1)),) + path = (*path, int_to_roman(counts.get(path, 1))) if path not in counts: counts[path] = 1 return path diff --git a/holoviews/element/annotation.py b/holoviews/element/annotation.py index 8396c9ef35..47a1b839e4 100644 --- a/holoviews/element/annotation.py +++ b/holoviews/element/annotation.py @@ -123,7 +123,7 @@ class VLine(Annotation): group = param.String(default='VLine', constant=True) - x = param.ClassSelector(default=0, class_=(Number,) + datetime_types, doc=""" + x = param.ClassSelector(default=0, class_=(Number, *datetime_types), doc=""" The x-position of the VLine which make be numeric or a timestamp.""") __pos_params = ['x'] @@ -158,7 +158,7 @@ class HLine(Annotation): group = param.String(default='HLine', constant=True) - y = param.ClassSelector(default=0, class_=(Number,) + datetime_types, doc=""" + y = param.ClassSelector(default=0, class_=(Number, *datetime_types), doc=""" The y-position of the HLine which make be numeric or a timestamp.""") __pos_params = ['y'] @@ -229,10 +229,10 @@ class VSpan(Annotation): group = param.String(default='VSpan', constant=True) - x1 = param.ClassSelector(default=0, class_=(Number,) + datetime_types, allow_None=True, doc=""" + x1 = param.ClassSelector(default=0, class_=(Number, *datetime_types), allow_None=True, doc=""" The start x-position of the VSpan which must be numeric or a timestamp.""") - x2 = param.ClassSelector(default=0, class_=(Number,) + datetime_types, allow_None=True, doc=""" + x2 = param.ClassSelector(default=0, class_=(Number, *datetime_types), allow_None=True, doc=""" The end x-position of the VSpan which must be numeric or a timestamp.""") __pos_params = ['x1', 'x2'] @@ -265,10 +265,10 @@ class HSpan(Annotation): group = param.String(default='HSpan', constant=True) - y1 = param.ClassSelector(default=0, class_=(Number,) + datetime_types, allow_None=True, doc=""" + y1 = param.ClassSelector(default=0, class_=(Number, *datetime_types), allow_None=True, doc=""" The start y-position of the VSpan which must be numeric or a timestamp.""") - y2 = param.ClassSelector(default=0, class_=(Number,) + datetime_types, allow_None=True, doc=""" + y2 = param.ClassSelector(default=0, class_=(Number, *datetime_types), allow_None=True, doc=""" The end y-position of the VSpan which must be numeric or a timestamp.""") __pos_params = ['y1', 'y2'] @@ -360,10 +360,10 @@ class Arrow(Annotation): specified as well as the arrow head style. """ - x = param.ClassSelector(default=0, class_=(Number, ) + datetime_types, doc=""" + x = param.ClassSelector(default=0, class_=(Number, *datetime_types), doc=""" The x-position of the arrow which make be numeric or a timestamp.""") - y = param.ClassSelector(default=0, class_=(Number, ) + datetime_types, doc=""" + y = param.ClassSelector(default=0, class_=(Number, *datetime_types), doc=""" The y-position of the arrow which make be numeric or a timestamp.""") text = param.String(default='', doc="Text associated with the arrow.") @@ -430,10 +430,10 @@ class Text(Annotation): Draw a text annotation at the specified position with custom fontsize, alignment and rotation. """ - x = param.ClassSelector(default=0, class_=(Number, str) + datetime_types, doc=""" + x = param.ClassSelector(default=0, class_=(Number, str, *datetime_types), doc=""" The x-position of the arrow which make be numeric or a timestamp.""") - y = param.ClassSelector(default=0, class_=(Number, str) + datetime_types, doc=""" + y = param.ClassSelector(default=0, class_=(Number, str, *datetime_types), doc=""" The y-position of the arrow which make be numeric or a timestamp.""") text = param.String(default='', doc="The text to be displayed.") diff --git a/holoviews/element/graphs.py b/holoviews/element/graphs.py index 467a8632f1..49d569d137 100644 --- a/holoviews/element/graphs.py +++ b/holoviews/element/graphs.py @@ -30,9 +30,9 @@ def __call__(self, specs=None, **dimensions): redimmed = super().__call__(specs, **dimensions) new_data = (redimmed.data,) if self._obj.nodes: - new_data = new_data + (self._obj.nodes.redim(specs, **dimensions),) + new_data = (*new_data, self._obj.nodes.redim(specs, **dimensions)) if self._obj._edgepaths: - new_data = new_data + (self._obj.edgepaths.redim(specs, **dimensions),) + new_data = (*new_data, self._obj.edgepaths.redim(specs, **dimensions)) return redimmed.clone(new_data) @@ -63,7 +63,7 @@ def _process(self, element, key=None): for (s, t), w in zip(edges, element[weight]): graph.edges[s, t][weight] = w positions = self.p.layout(graph, **self.p.kwargs) - nodes = [tuple(pos)+(idx,) for idx, pos in sorted(positions.items())] + nodes = [(*pos, idx) for idx, pos in sorted(positions.items())] else: source = element.dimension_values(0, expanded=False) target = element.dimension_values(1, expanded=False) @@ -231,12 +231,12 @@ def clone(self, data=None, shared_data=True, new_type=None, link=True, if data is None: data = (self.data, self.nodes) if self._edgepaths is not None: - data = data + (self.edgepaths,) + data = (*data, self.edgepaths) overrides['plot_id'] = self._plot_id elif not isinstance(data, tuple): data = (data, self.nodes) if self._edgepaths: - data = data + (self.edgepaths,) + data = (*data, self.edgepaths) return super().clone(data, shared_data, new_type, link, *args, **overrides) @@ -261,8 +261,8 @@ def select(self, selection_expr=None, selection_specs=None, selection_mode='edge holoviews.util.transform.dim expression. Use the selection_specs keyword argument to specify a selection specification""") - selection = {dim: sel for dim, sel in selection.items() - if dim in self.dimensions('ranges')+['selection_mask']} + sel_dims = (*self.dimensions('ranges'), 'selection_mask') + selection = {dim: sel for dim, sel in selection.items() if dim in sel_dims} if (selection_specs and not any(self.matches(sp) for sp in selection_specs) or (not selection and not selection_expr)): return self @@ -422,7 +422,7 @@ def from_networkx(cls, G, positions, nodes=None, **kwargs): edge_cols = sorted([k for k in edges if k not in ('start', 'end') and len(edges[k]) == len(edges['start'])]) edge_vdims = [str(col) if isinstance(col, int) else col for col in edge_cols] - edge_data = tuple(edges[col] for col in ['start', 'end']+edge_cols) + edge_data = tuple(edges[col] for col in ['start', 'end', *edge_cols]) # Unpack user node info xdim, ydim, idim = cls.node_type.kdims[:3] @@ -455,7 +455,7 @@ def from_networkx(cls, G, positions, nodes=None, **kwargs): node_columns[idim.name].append(idx) node_cols = sorted([k for k in node_columns if k not in cls.node_type.kdims and len(node_columns[k]) == len(node_columns[xdim.name])]) - columns = [xdim.name, ydim.name, idim.name]+node_cols+list(info_cols) + columns = [xdim.name, ydim.name, idim.name, *node_cols, *info_cols] node_data = tuple(node_columns[col] for col in columns) # Construct nodes @@ -522,7 +522,7 @@ def __init__(self, data, kdims=None, vdims=None, **params): if is_dataframe(nodes): coords = list(nodes.columns)[:2] index = nodes.index.name or 'index' - nodes = self.node_type(nodes, coords+[index]) + nodes = self.node_type(nodes, [*coords, index]) else: try: points = self.point_type(nodes) @@ -723,7 +723,7 @@ def _process(self, element, key=None): values, vdims = (), [] if len(nodes): - node_data = (mxs, mys, nodes)+values + node_data = (mxs, mys, nodes, *values) else: node_data = tuple([] for _ in kdims+vdims) diff --git a/holoviews/element/raster.py b/holoviews/element/raster.py index 8075bfff3b..caf8e1b361 100644 --- a/holoviews/element/raster.py +++ b/holoviews/element/raster.py @@ -123,7 +123,7 @@ def sample(self, samples=None, bounds=None, **sample_values): samples = zip(*[c if isinstance(c, list) else [c] for _, c in sorted([(self.get_dimension_index(k), v) for k, v in sample_values.items()])]) - table_data = [c+(self._zdata[self._coord2matrix(c)],) + table_data = [(*c, self._zdata[self._coord2matrix(c)]) for c in samples] params['kdims'] = self.kdims return Table(table_data, **params) @@ -333,7 +333,7 @@ def _validate(self, data_bounds, supplied_bounds): if yvals.ndim > 1: invalid.append(ydim) if invalid: - dims = '{} and {}'.format(*tuple(invalid)) if len(invalid) > 1 else f'{invalid[0]}' + dims = '{} and {}'.format(*invalid) if len(invalid) > 1 else f'{invalid[0]}' raise ValueError(f'{clsname} coordinates must be 1D arrays, ' f'{dims} dimension(s) were found to have ' 'multiple dimensions. Either supply 1D ' @@ -442,7 +442,7 @@ def select(self, selection_specs=None, **selection): selection = (y, x) sliced = False - datatype = list(util.unique_iterator([self.interface.datatype]+self.datatype)) + datatype = list(util.unique_iterator([self.interface.datatype, *self.datatype])) data = self.interface.ndloc(self, selection) if not sliced: if np.isscalar(data): @@ -692,7 +692,7 @@ def __init__(self, data, kdims=None, vdims=None, **params): if ((hasattr(data, 'shape') and data.shape[-1] == 4 and len(vdims) == 3) or (isinstance(data, tuple) and isinstance(data[-1], np.ndarray) and data[-1].ndim == 3 and data[-1].shape[-1] == 4 and len(vdims) == 3) or - (isinstance(data, dict) and tuple(dimension_name(vd) for vd in vdims)+(alpha.name,) in data)): + (isinstance(data, dict) and (*map(dimension_name, vdims), alpha.name) in data)): # Handle all forms of packed value dimensions vdims.append(alpha) super().__init__(data, kdims=kdims, vdims=vdims, **params) @@ -843,12 +843,12 @@ def trimesh(self): ts = (t1, t2, t3) for vd in self.vdims: zs = self.dimension_values(vd) - ts = ts + (np.concatenate([zs, zs]),) + ts = (*ts, np.concatenate([zs, zs])) # Construct TriMesh params = util.get_param_values(self) params['kdims'] = params['kdims'] + TriMesh.node_type.kdims[2:] - nodes = TriMesh.node_type(vertices+(np.arange(len(vertices[0])),), + nodes = TriMesh.node_type((*vertices, np.arange(len(vertices[0]))), **{k: v for k, v in params.items() if k != 'vdims'}) return TriMesh(((ts,), nodes), **{k: v for k, v in params.items() diff --git a/holoviews/element/sankey.py b/holoviews/element/sankey.py index 87f43e15c3..f98e2e543b 100644 --- a/holoviews/element/sankey.py +++ b/holoviews/element/sankey.py @@ -58,9 +58,12 @@ def layout(self, element, **params): node_data = [] for node in graph['nodes']: - node_data.append((np.mean([node['x0'], node['x1']]), - np.mean([node['y0'], node['y1']]), - node['index'])+tuple(node['values'])) + node_data.append(( + np.mean([node['x0'], node['x1']]), + np.mean([node['y0'], node['y1']]), + node['index'], + *node['values'] + )) if element.nodes.ndims == 3: kdims = element.nodes.kdims elif element.nodes.ndims: diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index c71b81c630..a9b56ed09a 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -139,7 +139,7 @@ def __call__(self, *args, **params): # Not quite right, should be set when switching backends if 'matplotlib' in Store.renderers and not notebook_extension._loaded: svg_exporter = Store.renderers['matplotlib'].instance(holomap=None,fig='svg') - hv.archive.exporters = [svg_exporter] + hv.archive.exporters + hv.archive.exporters = [svg_exporter, *hv.archive.exporters] p = param.ParamOverrides(self, {k:v for k,v in params.items() if k!='config'}) if p.case_sensitive_completion: @@ -227,7 +227,7 @@ def _get_resources(self, args, params): """ resources = [] disabled = [] - for resource in ['holoviews'] + list(Store.renderers.keys()): + for resource in ['holoviews', *Store.renderers]: if resource in args: resources.append(resource) @@ -245,7 +245,7 @@ def _get_resources(self, args, params): resources = [r for r in resources if r not in disabled] if ('holoviews' not in disabled) and ('holoviews' not in resources): - resources = ['holoviews'] + resources + resources = ['holoviews', *resources] return resources @classmethod diff --git a/holoviews/ipython/magics.py b/holoviews/ipython/magics.py index a79ecceb93..0cc1dfd8f1 100644 --- a/holoviews/ipython/magics.py +++ b/holoviews/ipython/magics.py @@ -109,7 +109,7 @@ class CompositorMagic(Magics): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) lines = ['The %compositor line magic is used to define compositors.'] - self.compositor.__func__.__doc__ = '\n'.join(lines + [CompositorSpec.__doc__]) + self.compositor.__func__.__doc__ = '\n'.join([*lines, CompositorSpec.__doc__]) @line_magic diff --git a/holoviews/operation/__init__.py b/holoviews/operation/__init__.py index 3ca0db3352..50b2138b38 100644 --- a/holoviews/operation/__init__.py +++ b/holoviews/operation/__init__.py @@ -17,4 +17,4 @@ def public(obj): if public(_v) and issubclass(_v, Operation): Compositor.operations.append(_v) -__all__ = _public + ['Compositor'] +__all__ = [*_public, 'Compositor'] diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index 2ed59933f1..2d8f238df4 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -697,14 +697,14 @@ def _process(self, element, key=None): value_cols = [] if agg_fn.column is None else [agg_fn.column] if y is None: - df = element.dframe([x]+value_cols).copy() + df = element.dframe([x, *value_cols]).copy() y = Dimension('y') df['y0'] = float(self.p.offset) df['y1'] = float(self.p.offset + spike_length) yagg = ['y0', 'y1'] if not self.p.expand: height = 1 else: - df = element.dframe([x, y]+value_cols).copy() + df = element.dframe([x, y, *value_cols]).copy() df['y0'] = np.array(0, df.dtypes[y.name]) yagg = ['y0', y.name] if xtype == 'datetime': @@ -948,7 +948,7 @@ def _process(self, element, key=None): regridded[vd] = rarray regridded = xr.Dataset(regridded) - return element.clone(regridded, datatype=['xarray']+element.datatype, **params) + return element.clone(regridded, datatype=['xarray', *element.datatype], **params) @@ -1234,7 +1234,7 @@ def uint32_to_uint8(cls, img): """ Cast uint32 RGB image to 4 uint8 channels. """ - return np.flipud(img.view(dtype=np.uint8).reshape(img.shape + (4,))) + return np.flipud(img.view(dtype=np.uint8).reshape((*img.shape, 4))) @classmethod @@ -1242,9 +1242,9 @@ def uint32_to_uint8_xr(cls, img): """ Cast uint32 xarray DataArray to 4 uint8 channels. """ - new_array = img.values.view(dtype=np.uint8).reshape(img.shape + (4,)) - coords = dict(list(img.coords.items())+[('band', [0, 1, 2, 3])]) - return xr.DataArray(new_array, coords=coords, dims=img.dims+('band',)) + new_array = img.values.view(dtype=np.uint8).reshape((*img.shape, 4)) + coords = dict(img.coords, band=[0, 1, 2, 3]) + return xr.DataArray(new_array, coords=coords, dims=(*img.dims, 'band')) @classmethod @@ -1272,7 +1272,7 @@ def to_xarray(cls, element): finally: element.vdims[:] = vdims dtypes = [dt for dt in element.datatype if dt != 'xarray'] - return element.clone(data, datatype=['xarray']+dtypes, + return element.clone(data, datatype=['xarray', *dtypes], bounds=element.bounds, xdensity=element.xdensity, ydensity=element.ydensity) @@ -1311,7 +1311,7 @@ def _process(self, element, key=None): array = array.to_array("z") # If data is 3D then we have one extra constant dimension if array.ndim > 3: - drop = [d for d in array.dims if d not in kdims+["z"]] + drop = [d for d in array.dims if d not in [*kdims, 'z']] array = array.squeeze(dim=drop) array = array.transpose(*kdims, ...) else: @@ -1620,8 +1620,8 @@ def _process(self, overlay, key=None): data = (coords[dims[1]], coords[dims[0]], arr[:, :, 0], arr[:, :, 1], arr[:, :, 2]) if arr.shape[-1] == 4: - data = data + (arr[:, :, 3],) - return rgb.clone(data, datatype=[rgb.interface.datatype]+rgb.datatype) + data = (*data, arr[:, :, 3]) + return rgb.clone(data, datatype=[rgb.interface.datatype, *rgb.datatype]) @@ -1685,7 +1685,7 @@ def _process(self, element, key=None): kd.name: rgb.dimension_values(kd, expanded=False) for kd in rgb.kdims } - vdims = rgb.vdims+[rgb.alpha_dimension] if len(rgb.vdims) == 3 else rgb.vdims + vdims = [*rgb.vdims, rgb.alpha_dimension] if len(rgb.vdims) == 3 else rgb.vdims kwargs['vdims'] = vdims new_data[tuple(vd.name for vd in vdims)] = img else: diff --git a/holoviews/operation/element.py b/holoviews/operation/element.py index d7f6852cd4..8364c8704e 100644 --- a/holoviews/operation/element.py +++ b/holoviews/operation/element.py @@ -1093,7 +1093,7 @@ def _process_layer(self, element, key=None): xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x, dvals) if is_datetime: xs = xs.astype(dt_type) - return element.clone((xs,)+dvals) + return element.clone((xs, *dvals)) def _process(self, element, key=None): return element.map(self._process_layer, Element) @@ -1180,7 +1180,7 @@ def _process(self, p, element, ranges=None): el_data = element.data # Get dimensions to plot against each other - types = (str, np.str_, np.object_)+datetime_types + types = (str, np.str_, np.object_, *datetime_types) dims = [d for d in element.dimensions() if _is_number(element.range(d)[0]) and not issubclass(element.get_dimension_type(d), types)] diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py index e242434a87..413c464806 100644 --- a/holoviews/plotting/bokeh/annotation.py +++ b/holoviews/plotting/bokeh/annotation.py @@ -183,7 +183,7 @@ class LabelsPlot(ColorbarPlot, AnnotationPlot): style_opts = (base_properties + text_properties + background_properties + border_properties + ['cmap', 'angle']) - _nonvectorized_styles = base_properties + ['cmap'] + _nonvectorized_styles = [*base_properties, 'cmap'] _plot_methods = dict(single='text', batched='text') _batched_style_opts = text_properties + background_properties + border_properties @@ -195,7 +195,7 @@ def get_data(self, element, ranges, style): dims = element.dimensions() coords = (1, 0) if self.invert_axes else (0, 1) - xdim, ydim, tdim = (dimension_sanitizer(dims[i].name) for i in coords+(2,)) + xdim, ydim, tdim = (dimension_sanitizer(dims[i].name) for i in (*coords, 2)) mapping = dict(x=xdim, y=ydim, text=tdim) data = {d: element.dimension_values(d) for d in (xdim, ydim)} if self.xoffset is not None: @@ -223,7 +223,7 @@ def get_data(self, element, ranges, style): class LineAnnotationPlot(ElementPlot, AnnotationPlot): - style_opts = line_properties + ['level', 'visible'] + style_opts = [*line_properties, 'level', 'visible'] apply_ranges = param.Boolean(default=False, doc=""" Whether to include the annotation in axis range calculations.""") @@ -308,7 +308,7 @@ def _init_glyph(self, plot, mapping, properties): class SlopePlot(ElementPlot, AnnotationPlot): - style_opts = line_properties + ['level'] + style_opts = [*line_properties, 'level'] _plot_methods = dict(single='Slope') @@ -345,7 +345,7 @@ class SplinePlot(ElementPlot, AnnotationPlot): Does not support matplotlib Path codes. """ - style_opts = line_properties + ['visible'] + style_opts = [*line_properties, 'visible'] _plot_methods = dict(single='bezier') selection_display = None diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 865f4cd524..fc60698db4 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -459,8 +459,7 @@ def initialize(self, plot_id=None): # Hash the plot handle with Callback type allowing multiple # callbacks on one handle to be merged - hash_ids = [id(h) for h in hash_handles] - cb_hash = tuple(hash_ids)+(id(type(self)),) + cb_hash = (*map(id, hash_handles), id(type(self))) if cb_hash in self._callbacks: # Merge callbacks if another callback has already been attached cb = self._callbacks[cb_hash] diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 340a4aeed9..6a912ba384 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -61,8 +61,10 @@ class PointPlot(LegendPlot, ColorbarPlot): selection_display = BokehOverlaySelectionDisplay() - style_opts = (['cmap', 'palette', 'marker', 'size', 'angle', 'hit_dilation'] + - base_properties + line_properties + fill_properties) + style_opts = [ + "cmap", "palette", "marker", "size", "angle", "hit_dilation", + *base_properties, *line_properties, *fill_properties + ] _plot_methods = dict(single='scatter', batched='scatter') _batched_style_opts = line_properties + fill_properties + ['size', 'marker', 'angle'] @@ -227,7 +229,7 @@ class VectorFieldPlot(ColorbarPlot): style_opts = base_properties + line_properties + ['scale', 'cmap'] - _nonvectorized_styles = base_properties + ['scale', 'cmap'] + _nonvectorized_styles = [*base_properties, "scale", "cmap"] _plot_methods = dict(single='segment') @@ -425,7 +427,7 @@ class HistogramPlot(ColorbarPlot): style_opts = base_properties + fill_properties + line_properties + ['cmap'] - _nonvectorized_styles = base_properties + ['line_dash'] + _nonvectorized_styles = [*base_properties, "line_dash"] _plot_methods = dict(single='quad') def get_data(self, element, ranges, style): @@ -456,7 +458,7 @@ def get_extents(self, element, ranges, range_type='combined', **kwargs): class SideHistogramPlot(HistogramPlot): - style_opts = HistogramPlot.style_opts + ['cmap'] + style_opts = [*HistogramPlot.style_opts, "cmap"] height = param.Integer(default=125, doc="The height of the plot") @@ -545,7 +547,7 @@ class ErrorPlot(ColorbarPlot): ('hover', 'selection', 'nonselection', 'muted') ] + ['lower_head', 'upper_head'] + base_properties) - _nonvectorized_styles = base_properties + ['line_dash'] + _nonvectorized_styles = [*base_properties, "line_dash"] _mapping = dict(base="base", upper="upper", lower="lower") _plot_methods = dict(single=Whisker) @@ -701,7 +703,7 @@ class SpikesPlot(SpikesMixin, ColorbarPlot): style_opts = base_properties + line_properties + ['cmap', 'palette'] - _nonvectorized_styles = base_properties + ['cmap'] + _nonvectorized_styles = [*base_properties, "cmap"] _plot_methods = dict(single='segment') def get_data(self, element, ranges, style): @@ -789,7 +791,7 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot): style_opts = (base_properties + fill_properties + line_properties + ['bar_width', 'cmap']) - _nonvectorized_styles = base_properties + ['bar_width', 'cmap'] + _nonvectorized_styles = [*base_properties, "bar_width", "cmap"] _plot_methods = dict(single=('vbar', 'hbar')) def _axis_properties(self, axis, key, plot, dimension=None, diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 7b8216bd38..366045d12d 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -823,7 +823,7 @@ def _axis_props(self, plots, subplots, element, ranges, pos, *, dim=None, axis_type = 'auto' dim_range = FactorRange(name=name) elif None in [v0, v1] or any( - True if isinstance(el, (str, bytes)+util.cftime_types) + True if isinstance(el, (str, bytes, *util.cftime_types)) else not util.isfinite(el) for el in [v0, v1] ): dim_range = range_type(name=name) @@ -917,7 +917,7 @@ def _init_plot(self, key, element, plots, ranges=None): subplots = list(self.subplots.values()) if self.subplots else [] axis_specs = {'x': {}, 'y': {}} - axis_specs['x']['x'] = self._axis_props(plots, subplots, element, ranges, pos=0) + (self.xaxis, {}) + axis_specs['x']['x'] = (*self._axis_props(plots, subplots, element, ranges, pos=0), self.xaxis, {}) if self.multi_y: if not BOKEH_GE_3_2_0: self.param.warning('Independent axis zooming for multi_y=True only supported for Bokeh >=3.2') @@ -934,9 +934,9 @@ def _init_plot(self, key, element, plots, ranges=None): range_tags_extras['y-upperlim'] = upperlim else: range_tags_extras['autorange'] = False - axis_specs['y']['y'] = self._axis_props( - plots, subplots, element, ranges, pos=1, range_tags_extras=range_tags_extras - ) + (self.yaxis, {}) + axis_specs['y']['y'] = ( + *self._axis_props(plots, subplots, element, ranges, pos=1, range_tags_extras=range_tags_extras), self.yaxis, {} + ) if self._subcoord_overlaid: _, extra_axis_specs = self._create_extra_axes(plots, subplots, element, ranges) @@ -2720,7 +2720,7 @@ class ColorbarPlot(ElementPlot): _default_nan = '#8b8b8b' - _nonvectorized_styles = base_properties + ['cmap', 'palette'] + _nonvectorized_styles = [*base_properties, 'cmap', 'palette'] def _draw_colorbar(self, plot, color_mapper, prefix=''): if CategoricalColorMapper and isinstance(color_mapper, CategoricalColorMapper): diff --git a/holoviews/plotting/bokeh/geometry.py b/holoviews/plotting/bokeh/geometry.py index 5b56c8c2e1..78735b1018 100644 --- a/holoviews/plotting/bokeh/geometry.py +++ b/holoviews/plotting/bokeh/geometry.py @@ -23,7 +23,7 @@ class SegmentPlot(GeomMixin, ColorbarPlot): style_opts = base_properties + line_properties + ['cmap'] _allow_implicit_categories = False - _nonvectorized_styles = base_properties + ['cmap'] + _nonvectorized_styles = [*base_properties, 'cmap'] _plot_methods = dict(single='segment') def get_data(self, element, ranges, style): @@ -49,7 +49,7 @@ class RectanglesPlot(GeomMixin, LegendPlot, ColorbarPlot): ['cmap']) _allow_implicit_categories = False - _nonvectorized_styles = base_properties + ['cmap'] + _nonvectorized_styles = [*base_properties, 'cmap'] _plot_methods = dict(single='quad') _batched_style_opts = line_properties + fill_properties _color_style = 'fill_color' diff --git a/holoviews/plotting/bokeh/graphs.py b/holoviews/plotting/bokeh/graphs.py index 5c95d6d6bf..f52e9326f9 100644 --- a/holoviews/plotting/bokeh/graphs.py +++ b/holoviews/plotting/bokeh/graphs.py @@ -69,7 +69,7 @@ class GraphPlot(GraphMixin, CompositeElementPlot, ColorbarPlot, LegendPlot): ['node_size', 'cmap', 'edge_cmap', 'node_cmap', 'node_radius', 'node_marker']) - _nonvectorized_styles = base_properties + ['cmap', 'edge_cmap', 'node_cmap'] + _nonvectorized_styles = [*base_properties, 'cmap', 'edge_cmap', 'node_cmap'] # Filled is only supported for subclasses filled = False @@ -312,7 +312,7 @@ def _get_graph_properties(self, plot, element, data, mapping, ranges, style): layout = StaticLayoutProvider(graph_layout=layout) self.handles['layout_source'] = layout - return tuple(sources+[layout]), properties + return (*sources, layout), properties def _reorder_renderers(self, plot, renderer, mapping): "Reorders renderers based on the defined draw order" diff --git a/holoviews/plotting/bokeh/heatmap.py b/holoviews/plotting/bokeh/heatmap.py index 0fcf891713..88f8f0143e 100644 --- a/holoviews/plotting/bokeh/heatmap.py +++ b/holoviews/plotting/bokeh/heatmap.py @@ -50,8 +50,7 @@ class HeatMapPlot(ColorbarPlot): _plot_methods = dict(single='rect') - style_opts = (['cmap', 'color', 'dilate'] + base_properties + - line_properties + fill_properties) + style_opts = ['cmap', 'color', 'dilate', *base_properties, *line_properties, *fill_properties] selection_display = BokehOverlaySelectionDisplay() diff --git a/holoviews/plotting/bokeh/hex_tiles.py b/holoviews/plotting/bokeh/hex_tiles.py index 1538f9686d..bd930ab06a 100644 --- a/holoviews/plotting/bokeh/hex_tiles.py +++ b/holoviews/plotting/bokeh/hex_tiles.py @@ -145,7 +145,7 @@ class HexTilesPlot(ColorbarPlot): style_opts = base_properties + line_properties + fill_properties + ['cmap', 'scale'] - _nonvectorized_styles = base_properties + ['cmap', 'line_dash'] + _nonvectorized_styles = [*base_properties, 'cmap', 'line_dash'] _plot_methods = dict(single='hex_tile') def get_extents(self, element, ranges, range_type='combined', **kwargs): diff --git a/holoviews/plotting/bokeh/links.py b/holoviews/plotting/bokeh/links.py index 02b49ff4c2..d60f2f5af5 100644 --- a/holoviews/plotting/bokeh/links.py +++ b/holoviews/plotting/bokeh/links.py @@ -42,7 +42,7 @@ def __init__(self, root_model, link, source_plot, target_plot=None): references = {k: v for k, v in link.param.values().items() if k not in ('source', 'target', 'name')} - for sh in self.source_handles+[self.source_model]: + for sh in [*self.source_handles, self.source_model]: key = f'source_{sh}' references[key] = source_plot.handles[sh] @@ -52,7 +52,7 @@ def __init__(self, root_model, link, source_plot, target_plot=None): references[p] = value if target_plot is not None: - for sh in self.target_handles+[self.target_model]: + for sh in [*self.target_handles, self.target_model]: key = f'target_{sh}' references[key] = target_plot.handles[sh] diff --git a/holoviews/plotting/bokeh/path.py b/holoviews/plotting/bokeh/path.py index 74c7a8c68f..883d900d31 100644 --- a/holoviews/plotting/bokeh/path.py +++ b/holoviews/plotting/bokeh/path.py @@ -39,7 +39,7 @@ class PathPlot(LegendPlot, ColorbarPlot): _plot_methods = dict(single='multi_line', batched='multi_line') _mapping = dict(xs='xs', ys='ys') - _nonvectorized_styles = base_properties + ['cmap'] + _nonvectorized_styles = [*base_properties, 'cmap'] _batched_style_opts = line_properties def _element_transform(self, transform, element, ranges): @@ -189,7 +189,7 @@ class ContourPlot(PathPlot): Deprecated in favor of color style mapping, e.g. `color=dim('color')`""") _color_style = 'line_color' - _nonvectorized_styles = base_properties + ['cmap'] + _nonvectorized_styles = [*base_properties, 'cmap'] def __init__(self, *args, **params): super().__init__(*args, **params) diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py index ff52dd974f..ac45e97db5 100644 --- a/holoviews/plotting/bokeh/raster.py +++ b/holoviews/plotting/bokeh/raster.py @@ -28,7 +28,7 @@ class RasterPlot(ColorbarPlot): show_legend = param.Boolean(default=False, doc=""" Whether to show legend for the plot.""") - style_opts = base_properties + ['cmap', 'alpha'] + style_opts = [*base_properties, 'cmap', 'alpha'] _nonvectorized_styles = style_opts @@ -136,7 +136,7 @@ class RGBPlot(LegendPlot): padding = param.ClassSelector(default=0, class_=(int, float, tuple)) - style_opts = ['alpha'] + base_properties + style_opts = ['alpha', *base_properties] _nonvectorized_styles = style_opts @@ -352,7 +352,7 @@ class QuadMeshPlot(ColorbarPlot): selection_display = BokehOverlaySelectionDisplay() - style_opts = ['cmap'] + base_properties + line_properties + fill_properties + style_opts = ['cmap', *base_properties, *line_properties, *fill_properties] _nonvectorized_styles = style_opts diff --git a/holoviews/plotting/bokeh/sankey.py b/holoviews/plotting/bokeh/sankey.py index 8115ca1a21..a96112551a 100644 --- a/holoviews/plotting/bokeh/sankey.py +++ b/holoviews/plotting/bokeh/sankey.py @@ -58,15 +58,14 @@ class SankeyPlot(GraphPlot): _draw_order = ['graph', 'quad_1', 'text_1', 'text_2'] - style_opts = GraphPlot.style_opts + ['edge_fill_alpha', 'nodes_line_color', - 'label_text_font_size'] + style_opts = [*GraphPlot.style_opts, 'edge_fill_alpha', 'nodes_line_color', 'label_text_font_size'] filled = True def _init_glyphs(self, plot, element, ranges, source): super()._init_glyphs(plot, element, ranges, source) renderer = plot.renderers.pop(plot.renderers.index(self.handles['glyph_renderer'])) - plot.renderers = [renderer] + plot.renderers + plot.renderers = [renderer, *plot.renderers] arc_renderer = self.handles['quad_1_glyph_renderer'] scatter_renderer = self.handles['scatter_1_glyph_renderer'] arc_renderer.view = scatter_renderer.view diff --git a/holoviews/plotting/bokeh/selection.py b/holoviews/plotting/bokeh/selection.py index 436c99a914..b8a0d586e8 100644 --- a/holoviews/plotting/bokeh/selection.py +++ b/holoviews/plotting/bokeh/selection.py @@ -55,7 +55,7 @@ def _build_element_layer(self, element, layer_color, layer_alpha, **opts): filtered = {k: v for k, v in merged_opts.items() if k in allowed} plot_opts = Store.lookup_options('bokeh', element, 'plot').kwargs - tools = plot_opts.get('tools', []) + ['box_select'] + tools = [*plot_opts.get('tools', []), 'box_select'] return element.opts(backend='bokeh', clone=True, tools=tools, **filtered) diff --git a/holoviews/plotting/bokeh/stats.py b/holoviews/plotting/bokeh/stats.py index 25d31d4f92..8581eef35f 100644 --- a/holoviews/plotting/bokeh/stats.py +++ b/holoviews/plotting/bokeh/stats.py @@ -92,7 +92,7 @@ class BoxWhiskerPlot(MultiDistributionMixin, CompositeElementPlot, ColorbarPlot, ['outlier_'+p for p in fill_properties+line_properties] + ['box_width', 'cmap', 'box_cmap']) - _nonvectorized_styles = base_properties + ['box_width', 'whisker_width', 'cmap', 'box_cmap'] + _nonvectorized_styles = [*base_properties, 'box_width', 'whisker_width', 'cmap', 'box_cmap'] _stream_data = False # Plot does not support streaming data @@ -237,8 +237,8 @@ def get_data(self, element, ranges, style): data['x0'].append(label) data['x1'].append(label) for data in [w1_data, w2_data]: - data['x0'].append(wrap_tuple(label)+(-whisker_width,)) - data['x1'].append(wrap_tuple(label)+(whisker_width,)) + data['x0'].append((*wrap_tuple(label), -whisker_width)) + data['x1'].append((*wrap_tuple(label), whisker_width)) r1_data['top'].append(q2) r2_data['top'].append(q1) r1_data['bottom'].append(q3) @@ -431,8 +431,8 @@ def _kde_data(self, element, el, key, split_dim, split_cats, **kwargs): if split_dim: if len(_xs): - fill_xs.append([x_range[0]]+list(_xs)+[x_range[-1]]) - fill_ys.append([0]+list(_ys)+[0]) + fill_xs.append([x_range[0], *_xs, x_range[-1]]) + fill_ys.append([0, *_ys, 0]) else: fill_xs.append([]) fill_ys.append([]) @@ -447,10 +447,10 @@ def _kde_data(self, element, el, key, split_dim, split_cats, **kwargs): # this scales the width if split_dim: fill_xs = [np.asarray(x) for x in fill_xs] - fill_ys = [[key + (y,) for y in (fy/np.abs(ys).max())*(self.violin_width/scale)] + fill_ys = [[(*key, y) for y in (fy/np.abs(ys).max())*(self.violin_width/scale)] if len(fy) else [] for fy in fill_ys] ys = (ys/np.nanmax(np.abs(ys)))*(self.violin_width/scale) if len(ys) else [] - ys = [key + (y,) for y in ys] + ys = [(*key, y) for y in ys] line = {'ys': xs, 'xs': ys} if split_dim: @@ -473,7 +473,7 @@ def _kde_data(self, element, el, key, split_dim, split_cats, **kwargs): sidx = np.argmin(np.abs(xs-stat)) sx, sy = xs[sidx], ys[sidx] segments['x'].append(sx) - segments['y0'].append(key+(-sy[-1],)) + segments['y0'].append((*key, -sy[-1])) segments['y1'].append(sy) elif self.inner == 'stick': if len(xs): @@ -481,10 +481,10 @@ def _kde_data(self, element, el, key, split_dim, split_cats, **kwargs): sidx = np.argmin(np.abs(xs-value)) sx, sy = xs[sidx], ys[sidx] segments['x'].append(sx) - segments['y0'].append(key+(-sy[-1],)) + segments['y0'].append((*key, -sy[-1])) segments['y1'].append(sy) elif self.inner == 'box': - xpos = key+(0,) + xpos = (*key, 0) q1, q2, q3, upper, lower, _ = self._box_stats(values) segments['x'].append(xpos) segments['y0'].append(lower) diff --git a/holoviews/plotting/bokeh/styles.py b/holoviews/plotting/bokeh/styles.py index b8fbded980..6294ca9fc1 100644 --- a/holoviews/plotting/bokeh/styles.py +++ b/holoviews/plotting/bokeh/styles.py @@ -40,7 +40,7 @@ for prop in fill_base_properties for prefix in property_prefixes] -border_properties = ['border_' + prop for prop in line_base_properties + ['radius']] +border_properties = ['border_' + prop for prop in [*line_base_properties, 'radius']] hatch_properties = ['hatch_color', 'hatch_scale', 'hatch_weight', 'hatch_extra', 'hatch_pattern', 'hatch_alpha'] @@ -146,7 +146,7 @@ def validate(style, value, scalar=False): validator = get_validator(style) if validator is None: return None - if isinstance(value, arraylike_types+(list,)): + if isinstance(value, (*arraylike_types, list)): if scalar: return False return all(validator(v) for v in value) diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index a767beb1fc..685a6e3bcd 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -647,7 +647,7 @@ def hsv_to_rgb(hsv): order = np.array([[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]]) rgb = clist[order[i], np.arange(np.prod(shape))[:,None]] - return rgb.reshape(shape+(3,)) + return rgb.reshape((*shape, 3)) def pad_width(model, table_padding=0.85, tabs_padding=1.2): @@ -777,7 +777,7 @@ def cds_column_replace(source, data): columns are not the same length as the columns being updated. """ current_length = [len(v) for v in source.data.values() - if isinstance(v, (list,)+arraylike_types)] + if isinstance(v, (list, *arraylike_types))] new_length = [len(v) for v in data.values() if isinstance(v, (list, np.ndarray))] untouched = [k for k in source.data if k not in data] return bool(untouched and current_length and new_length and current_length[0] != new_length[0]) @@ -1063,8 +1063,8 @@ def multi_polygons_data(element): for i, (path, hx, hy) in enumerate(zip(arrays, xhs, yhs)): if i != (len(arrays)-1): path = path[:-1] - multi_xs.append([path[:, 0]]+hx) - multi_ys.append([path[:, 1]]+hy) + multi_xs.append([path[:, 0], *hx]) + multi_ys.append([path[:, 1], *hy]) xsh.append(multi_xs) ysh.append(multi_ys) return xsh, ysh diff --git a/holoviews/plotting/mpl/annotation.py b/holoviews/plotting/mpl/annotation.py index f4d48a0fe8..75783c4ac0 100644 --- a/holoviews/plotting/mpl/annotation.py +++ b/holoviews/plotting/mpl/annotation.py @@ -207,7 +207,7 @@ def get_data(self, element, ranges, style): if 'size' in style: style['fontsize'] = style.pop('size') if 'horizontalalignment' not in style: style['horizontalalignment'] = 'center' if 'verticalalignment' not in style: style['verticalalignment'] = 'center' - return positions + (text, cs), style, {} + return (*positions, text, cs), style, {} def init_artists(self, ax, plot_args, plot_kwargs): if plot_args[-1] is not None: @@ -245,7 +245,7 @@ class ArrowPlot(AnnotationPlot): "Draw an arrow using the information supplied to the Arrow annotation" _arrow_style_opts = ['alpha', 'color', 'lw', 'linewidth', 'visible'] - _text_style_opts = TextPlot.style_opts + ['textsize', 'fontsize'] + _text_style_opts = [*TextPlot.style_opts, 'textsize', 'fontsize'] style_opts = sorted(set(_arrow_style_opts + _text_style_opts)) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index e054c0700b..7fead45701 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -225,7 +225,7 @@ def get_data(self, element, ranges, style): xs = element.dimension_values(0) ys = [element.dimension_values(vdim) for vdim in element.vdims] - return tuple([xs]+ys), style, {} + return (xs, *ys), style, {} def init_artists(self, ax, plot_data, plot_kwargs): fill_fn = ax.fill_betweenx if self.invert_axes else ax.fill_between @@ -390,7 +390,7 @@ def _compute_ticks(self, element, edges, widths, lims): elif self.xticks: dim = element.get_dimension(0) inds = np.linspace(0, len(edges), self.xticks, dtype=int) - edges = list(edges) + [edges[-1] + widths[-1]] + edges = [*edges, edges[-1] + widths[-1]] xvals = [edges[i] for i in inds] labels = [dim.pprint_value(v) for v in xvals] return [xvals, labels] @@ -872,7 +872,7 @@ def _get_values(self, element, ranges): gvals, cvals = self._get_coords(element, ranges, as_string=False) kdims = element.kdims if element.ndims == 1: - dimensions = kdims + [None, None] + dimensions = [*kdims, None, None] values = {'group': gvals, 'stack': [None]} elif self.stacked: stack_dim = kdims[1] @@ -886,7 +886,7 @@ def _get_values(self, element, ranges): stack_order = list(stack_order) values = {'group': gvals, 'stack': stack_order} else: - dimensions = kdims + [None] + dimensions = [*kdims, None] values = {'group': gvals, 'category': cvals} return dimensions, values @@ -1093,7 +1093,7 @@ class SpikesPlot(SpikesMixin, PathPlot, ColorbarPlot): position = param.Number(default=0., doc=""" The position of the lower end of each spike.""") - style_opts = PathPlot.style_opts + ['cmap'] + style_opts = [*PathPlot.style_opts, 'cmap'] def init_artists(self, ax, plot_args, plot_kwargs): if 'c' in plot_kwargs: diff --git a/holoviews/plotting/mpl/chart3d.py b/holoviews/plotting/mpl/chart3d.py index 4d6da7583c..a777086c6e 100644 --- a/holoviews/plotting/mpl/chart3d.py +++ b/holoviews/plotting/mpl/chart3d.py @@ -223,7 +223,7 @@ def get_data(self, element, ranges, style): if self.invert_axes: coords = coords[::-1] data = data.T - cmesh_data = coords + [data] + cmesh_data = [*coords, data] if self.plot_type != 'wireframe' and 'cmap' in style: self._norm_kwargs(element, ranges, style, element.vdims[0]) diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index 79aba31afc..d31a3f7aaf 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -153,7 +153,7 @@ def _finalize_axis(self, key, element=None, title=None, dimensions=None, ranges= self._subplot_label(axis) # Apply axis options if axes are enabled - if element is not None and not any(not sp._has_axes for sp in [self] + subplots): + if element is not None and not any(not sp._has_axes for sp in [self, *subplots]): # Set axis labels if dimensions: self._set_labels(axis, dimensions, xlabel, ylabel, zlabel) diff --git a/holoviews/plotting/mpl/geometry.py b/holoviews/plotting/mpl/geometry.py index f3d8573932..9620cd75e0 100644 --- a/holoviews/plotting/mpl/geometry.py +++ b/holoviews/plotting/mpl/geometry.py @@ -13,7 +13,7 @@ class SegmentPlot(GeomMixin, ColorbarPlot): Segments are lines in 2D space where each two key dimensions specify a (x, y) node of the line. """ - style_opts = PathPlot.style_opts + ['cmap'] + style_opts = [*PathPlot.style_opts, 'cmap'] _nonvectorized_styles = ['cmap'] diff --git a/holoviews/plotting/mpl/graphs.py b/holoviews/plotting/mpl/graphs.py index 6279018aef..bdbd8a10a5 100644 --- a/holoviews/plotting/mpl/graphs.py +++ b/holoviews/plotting/mpl/graphs.py @@ -231,7 +231,7 @@ class TriMeshPlot(GraphPlot): filled = param.Boolean(default=False, doc=""" Whether the triangles should be drawn as filled.""") - style_opts = GraphPlot.style_opts + ['edge_facecolors'] + style_opts = [*GraphPlot.style_opts, 'edge_facecolors'] def get_data(self, element, ranges, style): edge_color = style.get('edge_color') @@ -259,7 +259,7 @@ class ChordPlot(ChordMixin, GraphPlot): allow_None=True, doc=""" Index of the dimension from which the node labels will be drawn""") - style_opts = GraphPlot.style_opts + ['text_font_size', 'label_offset'] + style_opts = [*GraphPlot.style_opts, 'text_font_size', 'label_offset'] _style_groups = ['edge', 'node', 'arc'] diff --git a/holoviews/plotting/mpl/raster.py b/holoviews/plotting/mpl/raster.py index d7d10d2e94..087e9d4443 100644 --- a/holoviews/plotting/mpl/raster.py +++ b/holoviews/plotting/mpl/raster.py @@ -178,7 +178,7 @@ def get_data(self, element, ranges, style): if self.invert_axes: coords = coords[::-1] data = data.T - cmesh_data = coords + [data] + cmesh_data = [*coords, data] if expanded: style['locs'] = np.concatenate(coords) vdim = element.vdims[0] diff --git a/holoviews/plotting/mpl/sankey.py b/holoviews/plotting/mpl/sankey.py index 606f960af4..3e83aa2758 100644 --- a/holoviews/plotting/mpl/sankey.py +++ b/holoviews/plotting/mpl/sankey.py @@ -44,7 +44,7 @@ class SankeyPlot(GraphPlot): filled = True - style_opts = GraphPlot.style_opts + ['label_text_font_size'] + style_opts = [*GraphPlot.style_opts, 'label_text_font_size'] def get_extents(self, element, ranges, range_type='combined', **kwargs): """ diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py index 696484264a..9cf0bb0a0a 100644 --- a/holoviews/plotting/mpl/util.py +++ b/holoviews/plotting/mpl/util.py @@ -121,7 +121,7 @@ def validate(style, value, vectorized=True): validator = get_validator(style) if validator is None: return None - if isinstance(value, arraylike_types+(list,)) and vectorized: + if isinstance(value, (*arraylike_types, list)) and vectorized: return all(validator(v) for v in value) try: valid = validator(value) @@ -387,7 +387,7 @@ def polygons_to_path_patches(element): if (interior[0] != interior[-1]).any(): interior = np.append(interior, interior[:1], axis=0) interiors.append(interior) - vertices = np.concatenate([array]+interiors) + vertices = np.concatenate([array, *interiors]) codes = np.concatenate([ring_coding(array)]+ [ring_coding(h) for h in interiors]) subpath.append(PathPatch(Path(vertices, codes))) diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index 34c1213240..6feb4f590b 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -1301,9 +1301,9 @@ def _get_axis_dims(self, element): """ dims = element.dimensions()[:2] if len(dims) == 1: - return dims + [None, None] + return [*dims, None, None] else: - return dims + [None] + return [*dims, None] def _has_axis_dimension(self, element, dimension): dims = self._get_axis_dims(element) @@ -1853,7 +1853,7 @@ def _create_subplot(self, key, obj, streams, ranges): opts = {'overlaid': overlay_type} if self.hmap.type == Overlay: - style_key = (obj.type.__name__,) + key + style_key = (obj.type.__name__, *key) if self.overlay_dims: opts['overlay_dims'] = self.overlay_dims else: diff --git a/holoviews/plotting/plotly/dash.py b/holoviews/plotting/plotly/dash.py index 008f070189..74a3c1b3f7 100644 --- a/holoviews/plotting/plotly/dash.py +++ b/holoviews/plotting/plotly/dash.py @@ -610,7 +610,7 @@ def update_figure(*args): ).to_dict() figs[fig_ind] = fig - return figs + [encode_store_data(store_data)] + return [*figs, encode_store_data(store_data)] # Register key dimension slider callbacks # Install callbacks to update kdim labels based on slider values diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index e448d580f5..fea30860e4 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -473,7 +473,7 @@ def export_widgets(self_or_cls, obj, filename, fmt=None, template=None, data to a json file in the supplied json_path (defaults to current path). """ - if fmt not in self_or_cls.widgets+['auto', None]: + if fmt not in [*self_or_cls.widgets, "auto", None]: raise ValueError("Renderer.export_widget may only export " "registered widget types.") self_or_cls.get_widget(obj, fmt).save(filename) diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py index 1924d1e924..1c043193e6 100644 --- a/holoviews/plotting/util.py +++ b/holoviews/plotting/util.py @@ -145,7 +145,7 @@ def compute_overlayable_zorders(obj, path=None): """ if path is None: path = [] - path = path+[obj] + path = [*path, obj] zorder_map = defaultdict(list) # Process non-dynamic layers @@ -157,7 +157,7 @@ def compute_overlayable_zorders(obj, path=None): for el in obj.values(): if isinstance(el, CompositeOverlay): for k, v in compute_overlayable_zorders(el, path).items(): - zorder_map[k] += v + [obj] + zorder_map[k] += [*v, obj] else: zorder_map[0] += [obj, el] elif obj not in zorder_map[0]: diff --git a/holoviews/selection.py b/holoviews/selection.py index 1120473c4f..a5931797a1 100644 --- a/holoviews/selection.py +++ b/holoviews/selection.py @@ -572,7 +572,7 @@ def build_selection(self, selection_streams, hvobj, operations, region_stream=No obj = hvobj.clone(link=False) if layer_number == 1 else hvobj cmap_stream = selection_streams.cmap_streams[layer_number] layer = obj.apply( - self._build_layer_callback, streams=[cmap_stream]+streams, + self._build_layer_callback, streams=[cmap_stream, *streams], layer_number=layer_number, cache=cache, per_element=True ) layers.append(layer) @@ -667,7 +667,7 @@ def _build_selection(el, colors, alpha, exprs, **kwargs): backup_clr = linear_gradient(unselected_color, "#000000", 7)[2] selected_colors = [c or backup_clr for c in colors[1:]] n = len(ds) - clrs = np.array([unselected_color] + list(selected_colors)) + clrs = np.array([unselected_color, *selected_colors]) color_inds = np.zeros(n, dtype='int8') diff --git a/holoviews/streams.py b/holoviews/streams.py index 5cbbe41f7c..22166b8809 100644 --- a/holoviews/streams.py +++ b/holoviews/streams.py @@ -20,7 +20,7 @@ from .core.ndmapping import UniformNdMapping # Types supported by Pointer derived streams -pointer_types = (Number, str, tuple)+util.datetime_types +pointer_types = (Number, str, tuple, *util.datetime_types) POPUP_POSITIONS = [ "top_right", @@ -1882,7 +1882,7 @@ def element(self): xs.append(x0) ys.append(y0) vals = [data[vd.name][i] for vd in source.vdims] - paths.append((xs, ys)+tuple(vals)) + paths.append((xs, ys, *vals)) datatype = source.datatype if source.interface.multi else ['multitabular'] return source.clone(paths, datatype=datatype, id=None) diff --git a/holoviews/tests/core/data/base.py b/holoviews/tests/core/data/base.py index aeb7161869..ff60da907d 100644 --- a/holoviews/tests/core/data/base.py +++ b/holoviews/tests/core/data/base.py @@ -855,7 +855,7 @@ def test_dataset_transform_add_ht(self): expected = Dataset({'Gender':self.gender, 'Age':self.age, 'Weight':self.weight, 'Height':self.height, 'combined': self.age*self.weight}, - kdims=self.kdims, vdims=self.vdims+['combined']) + kdims=self.kdims, vdims=[*self.vdims, 'combined']) self.assertEqual(transformed, expected) def test_select_with_neighbor(self): diff --git a/holoviews/tests/core/data/test_imageinterface.py b/holoviews/tests/core/data/test_imageinterface.py index 6d9b100487..b9d81025c1 100644 --- a/holoviews/tests/core/data/test_imageinterface.py +++ b/holoviews/tests/core/data/test_imageinterface.py @@ -429,7 +429,7 @@ def test_reduce_to_single_values(self): def test_sample_xcoord(self): ys = np.linspace(0.5, 9.5, 10) - data = (ys,) + tuple(self.rgb_array[:, 7, i] for i in range(3)) + data = (ys, *(self.rgb_array[:, 7, i] for i in range(3))) with DatatypeContext([self.datatype, 'dictionary' , 'dataframe'], self.rgb): self.assertEqual(self.rgb.sample(x=5), self.rgb.clone(data, kdims=['y'], @@ -437,7 +437,7 @@ def test_sample_xcoord(self): def test_sample_ycoord(self): xs = np.linspace(-9, 9, 10) - data = (xs,) + tuple(self.rgb_array[4, :, i] for i in range(3)) + data = (xs, *(self.rgb_array[4, :, i] for i in range(3))) with DatatypeContext([self.datatype, 'dictionary' , 'dataframe'], self.rgb): self.assertEqual(self.rgb.sample(y=5), self.rgb.clone(data, kdims=['x'], diff --git a/holoviews/tests/core/data/test_spatialpandas.py b/holoviews/tests/core/data/test_spatialpandas.py index 94ae9639ef..226a0bd748 100644 --- a/holoviews/tests/core/data/test_spatialpandas.py +++ b/holoviews/tests/core/data/test_spatialpandas.py @@ -108,8 +108,8 @@ def test_polygon_roundtrip(self): self.assertIsInstance(poly.data.geometry.dtype, PolygonDtype) roundtrip = poly.clone(datatype=['multitabular']) self.assertEqual(roundtrip.interface.datatype, 'multitabular') - expected = Polygons([{'x': xs+[1], 'y': ys+[2], 'z': 0}, - {'x': [3]+xs, 'y': [7]+ys, 'z': 1}], + expected = Polygons([{'x': [*xs, 1], 'y': [*ys, 2], 'z': 0}, + {'x': [3, *xs], 'y': [7, *ys], 'z': 1}], ['x', 'y'], 'z', datatype=['multitabular']) self.assertEqual(roundtrip, expected) diff --git a/holoviews/tests/core/test_dimensioned.py b/holoviews/tests/core/test_dimensioned.py index 1dc3dc7a46..80dd3abe62 100644 --- a/holoviews/tests/core/test_dimensioned.py +++ b/holoviews/tests/core/test_dimensioned.py @@ -42,8 +42,8 @@ def register_custom(cls, objtype, backend, custom_plot=None, custom_style=None): Store._options[backend] = OptionTree([], groups=groups) Store._custom_options[backend] = {} name = objtype.__name__ - style_opts = Keywords(['style_opt1', 'style_opt2']+custom_style, name) - plot_opts = Keywords(['plot_opt1', 'plot_opt2']+custom_plot, name) + style_opts = Keywords(['style_opt1', 'style_opt2', *custom_style], name) + plot_opts = Keywords(['plot_opt1', 'plot_opt2', *custom_plot], name) opt_groups = {'plot': Options(allowed_keywords=plot_opts), 'style': Options(allowed_keywords=style_opts), 'output': Options(allowed_keywords=['backend'])} diff --git a/holoviews/tests/core/test_utils.py b/holoviews/tests/core/test_utils.py index 41462601d7..47bc751d8c 100644 --- a/holoviews/tests/core/test_utils.py +++ b/holoviews/tests/core/test_utils.py @@ -456,12 +456,12 @@ def test_make_path_unique_no_clash(self): def test_make_path_unique_clash_without_label(self): path = ('Element',) new_path = make_path_unique(path, {path: 1}, True) - self.assertEqual(new_path, path+('I',)) + self.assertEqual(new_path, (*path, 'I')) def test_make_path_unique_clash_with_label(self): path = ('Element', 'A') new_path = make_path_unique(path, {path: 1}, True) - self.assertEqual(new_path, path+('I',)) + self.assertEqual(new_path, (*path, 'I')) def test_make_path_unique_no_clash_old(self): path = ('Element', 'A') @@ -471,7 +471,7 @@ def test_make_path_unique_no_clash_old(self): def test_make_path_unique_clash_without_label_old(self): path = ('Element',) new_path = make_path_unique(path, {path: 1}, False) - self.assertEqual(new_path, path+('I',)) + self.assertEqual(new_path, (*path, 'I')) def test_make_path_unique_clash_with_label_old(self): path = ('Element', 'A') @@ -639,12 +639,12 @@ def test_isfinite_pandas_period_series(self): def test_isfinite_pandas_period_index_nat(self): daily = pd.date_range('2017-1-1', '2017-1-3', freq='D').to_period('D') - daily = pd.PeriodIndex(list(daily)+[pd.NaT]) + daily = pd.PeriodIndex([*daily, pd.NaT]) self.assertEqual(isfinite(daily), np.array([True, True, True, False])) def test_isfinite_pandas_period_series_nat(self): daily = pd.date_range('2017-1-1', '2017-1-3', freq='D').to_period('D') - daily = pd.Series(list(daily)+[pd.NaT]) + daily = pd.Series([*daily, pd.NaT]) self.assertEqual(isfinite(daily), np.array([True, True, True, False])) def test_isfinite_pandas_timestamp_index(self): @@ -657,12 +657,12 @@ def test_isfinite_pandas_timestamp_series(self): def test_isfinite_pandas_timestamp_index_nat(self): daily = pd.date_range('2017-1-1', '2017-1-3', freq='D') - daily = pd.DatetimeIndex(list(daily)+[pd.NaT]) + daily = pd.DatetimeIndex([*daily, pd.NaT]) self.assertEqual(isfinite(daily), np.array([True, True, True, False])) def test_isfinite_pandas_timestamp_series_nat(self): daily = pd.date_range('2017-1-1', '2017-1-3', freq='D') - daily = pd.Series(list(daily)+[pd.NaT]) + daily = pd.Series([*daily, pd.NaT]) self.assertEqual(isfinite(daily), np.array([True, True, True, False])) def test_isfinite_datetime64_array(self): @@ -671,7 +671,7 @@ def test_isfinite_datetime64_array(self): def test_isfinite_datetime64_array_with_nat(self): dts = [np.datetime64(datetime.datetime(2017, 1, i)) for i in range(1, 4)] - dt64 = np.array(dts+[np.datetime64('NaT')]) + dt64 = np.array([*dts, np.datetime64('NaT')]) self.assertEqual(isfinite(dt64), np.array([True, True, True, False])) diff --git a/holoviews/tests/element/test_selection.py b/holoviews/tests/element/test_selection.py index 05f8ff3418..2c2361f861 100644 --- a/holoviews/tests/element/test_selection.py +++ b/holoviews/tests/element/test_selection.py @@ -297,7 +297,7 @@ def test_points_selection_geom(self): self.assertEqual(bbox, {'x': np.array([-0.1, 1.4, 1.4, -0.1]), 'y': np.array([-0.1, 0, 2.2, 2.2])}) self.assertEqual(expr.apply(points), np.array([False, True, False, False, False])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.1, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.1, -0.1)]])) @shapelib_available def test_points_selection_geom_inverted(self): @@ -307,7 +307,7 @@ def test_points_selection_geom_inverted(self): self.assertEqual(bbox, {'y': np.array([-0.1, 1.4, 1.4, -0.1]), 'x': np.array([-0.1, 0, 2.2, 2.2])}) self.assertEqual(expr.apply(points), np.array([False, False, True, False, False])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.1, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.1, -0.1)]])) def test_points_selection_categorical(self): points = Points((['B', 'A', 'C', 'D', 'E'], [3, 2, 1, 3, 4])) @@ -397,7 +397,7 @@ def test_img_selection_geom(self): [np.nan, np.nan, np.nan], [np.nan, np.nan, np.nan] ])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.4, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.4, -0.1)]])) @ds_available def test_img_selection_geom_inverted(self): @@ -412,7 +412,7 @@ def test_img_selection_geom_inverted(self): [ False, False, False], [False, False, False] ])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.4, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.4, -0.1)]])) def test_rgb_selection_numeric(self): img = RGB(([0, 1, 2], [0, 1, 2, 3], np.random.rand(4, 3, 3))) @@ -519,7 +519,7 @@ def test_rect_geom_selection(self): 'x1': np.array([-0.4, 2.2, 2.2, -0.1]), 'y1': np.array([-0.1, -0.1, 4.1, 4.2])}) self.assertEqual(expr.apply(rect), np.array([True, True, False])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.4, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.4, -0.1)]])) @shapely_available def test_rect_geom_selection_inverted(self): @@ -531,7 +531,7 @@ def test_rect_geom_selection_inverted(self): 'y1': np.array([-0.4, 3.2, 3.2, -0.1]), 'x1': np.array([-0.1, -0.1, 4.1, 4.2])}) self.assertEqual(expr.apply(rect), np.array([True, False, False])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.4, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.4, -0.1)]])) def test_segments_selection_numeric(self): segs = Segments([(0, 1, 2, 3), (1, 3, 1.5, 4), (2.5, 4.2, 3.5, 4.8)]) @@ -565,7 +565,7 @@ def test_segs_geom_selection(self): 'x1': np.array([-0.4, 2.2, 2.2, -0.1]), 'y1': np.array([-0.1, -0.1, 4.1, 4.2])}) self.assertEqual(expr.apply(rect), np.array([True, True, False])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.4, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.4, -0.1)]])) @shapely_available def test_segs_geom_selection_inverted(self): @@ -577,7 +577,7 @@ def test_segs_geom_selection_inverted(self): 'y1': np.array([-0.4, 3.2, 3.2, -0.1]), 'x1': np.array([-0.1, -0.1, 4.1, 4.2])}) self.assertEqual(expr.apply(rect), np.array([True, False, False])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(-0.4, -0.1)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (-0.4, -0.1)]])) class TestSelectionPolyExpr(ComparisonTestCase): @@ -625,7 +625,7 @@ def test_poly_geom_selection(self): self.assertEqual(bbox, {'x': np.array([0.2, 0.5, 0.75, 0.1]), 'y': np.array([-0.15, 0, 0.6, 0.45])}) self.assertEqual(expr.apply(poly, expanded=False), np.array([False, True, True])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(0.2, -0.15)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (0.2, -0.15)]])) @shapely_available def test_poly_geom_selection_inverted(self): @@ -639,7 +639,7 @@ def test_poly_geom_selection_inverted(self): self.assertEqual(bbox, {'y': np.array([0.2, 0.5, 0.75, 0.1]), 'x': np.array([-0.15, 0, 0.6, 0.6])}) self.assertEqual(expr.apply(poly, expanded=False), np.array([False, False, True])) - self.assertEqual(region, Rectangles([]) * Path([list(geom)+[(0.2, -0.15)]])) + self.assertEqual(region, Rectangles([]) * Path([[*geom, (0.2, -0.15)]])) class TestSpatialSelectColumnar: diff --git a/holoviews/tests/ipython/test_optscompleter.py b/holoviews/tests/ipython/test_optscompleter.py index 585fd26ef5..529cb00661 100644 --- a/holoviews/tests/ipython/test_optscompleter.py +++ b/holoviews/tests/ipython/test_optscompleter.py @@ -26,7 +26,7 @@ def setUp(self): ['styleoptC1', 'styleoptC2'])} self.compositor_defs = {} - self.all_keys = sorted(self.completions.keys()) + ['style(', 'plot[', 'norm{'] + self.all_keys = [*sorted(self.completions.keys()), "style(", "plot[", "norm{"] super().setUp() diff --git a/holoviews/tests/operation/test_datashader.py b/holoviews/tests/operation/test_datashader.py index dd36ecc396..1f2b442200 100644 --- a/holoviews/tests/operation/test_datashader.py +++ b/holoviews/tests/operation/test_datashader.py @@ -878,7 +878,7 @@ def test_shade_categorical_images_xarray(self): b = [[28, 95], [129, 95]] a = [[40, 0], [255, 0]] expected = RGB((xs, ys, r, g, b, a), datatype=['grid'], - vdims=RGB.vdims+[Dimension('A', range=(0, 1))]) + vdims=[*RGB.vdims, Dimension('A', range=(0, 1))]) self.assertEqual(shaded, expected) def test_shade_categorical_images_grid(self): @@ -896,7 +896,7 @@ def test_shade_categorical_images_grid(self): b = [[28, 95], [129, 95]] a = [[40, 0], [255, 0]] expected = RGB((xs, ys, r, g, b, a), datatype=['grid'], - vdims=RGB.vdims+[Dimension('A', range=(0, 1))]) + vdims=[*RGB.vdims, Dimension('A', range=(0, 1))]) self.assertEqual(shaded, expected) def test_shade_dt_xaxis_constant_yaxis(self): diff --git a/holoviews/tests/plotting/bokeh/test_pointplot.py b/holoviews/tests/plotting/bokeh/test_pointplot.py index b4fa85c172..95efecc2b9 100644 --- a/holoviews/tests/plotting/bokeh/test_pointplot.py +++ b/holoviews/tests/plotting/bokeh/test_pointplot.py @@ -182,7 +182,7 @@ def test_points_categorical_xaxis_mixed_type(self): plot = bokeh_renderer.get_plot(points*points2) x_range = plot.handles['x_range'] self.assertIsInstance(x_range, FactorRange) - self.assertEqual(x_range.factors, list(map(str, range(10))) + ['A', 'B', 'C', '2.0']) + self.assertEqual(x_range.factors, [*map(str, range(10)), 'A', 'B', 'C', '2.0']) def test_points_categorical_xaxis_invert_axes(self): points = Points((['A', 'B', 'C'], (1,2,3))).opts(invert_axes=True) diff --git a/holoviews/tests/plotting/bokeh/test_violinplot.py b/holoviews/tests/plotting/bokeh/test_violinplot.py index bea1ba8742..c7e5af14af 100644 --- a/holoviews/tests/plotting/bokeh/test_violinplot.py +++ b/holoviews/tests/plotting/bokeh/test_violinplot.py @@ -32,7 +32,7 @@ def test_violin_simple(self): kde = univariate_kde(violin, cut=5) xs, ys = (kde.dimension_values(i) for i in range(2)) ys = (ys/ys.max())*(0.7/2.) - ys = [('',)+(sign*y,) for sign, vs in ((-1, ys), (1, ys[::-1])) for y in vs] + ys = [('', sign * y) for sign, vs in ((-1, ys), (1, ys[::-1])) for y in vs] kde = {'x': np.concatenate([xs, xs[::-1]]), 'y': ys} plot = bokeh_renderer.get_plot(violin) diff --git a/holoviews/tests/plotting/matplotlib/test_histogramplot.py b/holoviews/tests/plotting/matplotlib/test_histogramplot.py index 2d18b13745..a9257f5a99 100644 --- a/holoviews/tests/plotting/matplotlib/test_histogramplot.py +++ b/holoviews/tests/plotting/matplotlib/test_histogramplot.py @@ -113,7 +113,7 @@ def test_histogram_color_op(self): artist = plot.handles['artist'] children = artist.get_children() for c, w in zip(children, ['#000000', '#FF0000', '#00FF00']): - self.assertEqual(c.get_facecolor(), tuple(c/255. for c in hex2rgb(w))+(1,)) + self.assertEqual(c.get_facecolor(), (*(c / 255.0 for c in hex2rgb(w)), 1)) def test_histogram_linear_color_op(self): histogram = Histogram([(0, 0, 0), (0, 1, 1), (0, 2, 2)], diff --git a/holoviews/util/settings.py b/holoviews/util/settings.py index 7ca0ef342a..7b4bc16a9a 100644 --- a/holoviews/util/settings.py +++ b/holoviews/util/settings.py @@ -56,7 +56,7 @@ def get_options(cls, items, options, warnfn): raise ValueError(f"Value {value!r} for key {keyword!r} not one of {allowed}") elif isinstance(allowed, tuple): if not (allowed[0] <= value <= allowed[1]): - info = (keyword,value)+allowed + info = (keyword, value, *allowed) raise ValueError("Value {!r} for key {!r} not between {} and {}".format(*info)) options[keyword] = value return cls._validate(options, items, warnfn) @@ -230,7 +230,7 @@ def _generate_docstring(cls, signature=False): 'size', 'dpi', 'filename', 'info', 'css', 'widget_location'] if signature: doc_signature = '\noutput({})\n'.format(', '.join(f'{kw}=None' for kw in keywords)) - return '\n'.join([doc_signature] + intro + descriptions) + return '\n'.join([doc_signature, *intro, *descriptions]) else: return '\n'.join(intro + descriptions) diff --git a/holoviews/util/transform.py b/holoviews/util/transform.py index 0298aa3fa7..8c95d3d4ae 100644 --- a/holoviews/util/transform.py +++ b/holoviews/util/transform.py @@ -259,12 +259,11 @@ def __init__(self, obj, *args, **kwargs): else: fn = None if fn is not None: - if not (isinstance(fn, function_types+(str,)) or + if not (isinstance(fn, (*function_types, str)) or any(fn in funcs for funcs in self._all_funcs)): raise ValueError('Second argument must be a function, ' f'found {type(fn)} type') - self.ops = self.ops + [{'args': args[1:], 'fn': fn, 'kwargs': kwargs, - 'reverse': kwargs.pop('reverse', False)}] + self.ops = [*self.ops, {'args': args[1:], 'fn': fn, 'kwargs': kwargs, 'reverse': kwargs.pop('reverse', False)}] def __getstate__(self): return self.__dict__ diff --git a/pyproject.toml b/pyproject.toml index d175cec6ea..f943b7e995 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,7 +169,6 @@ ignore = [ "PLR091", # Too many arguments/branches/statements "PLR2004", # Magic value used in comparison "PLW2901", # `for` loop variable is overwritten - "RUF005", # Consider {expr} instead of concatenation "RUF012", # Mutable class attributes should use `typing.ClassVar` ] extend-unsafe-fixes = [