Source code for scqubits.core.descriptors

# descriptors.py
#
# This file is part of scqubits: a Python package for superconducting qubits,
# Quantum 5, 583 (2021). https://quantum-journal.org/papers/q-2021-11-17-583/
#
#    Copyright (c) 2019 and later, Jens Koch and Peter Groszkowski
#    All rights reserved.
#
#    This source code is licensed under the BSD-style license found in the
#    LICENSE file in the root directory of this source tree.
############################################################################

# Recap on descriptors: see https://realpython.com/python-descriptors/

from typing import Any, Generic, Optional, Type, TypeVar

from scqubits.core.central_dispatch import DispatchClient

TargetType = TypeVar("TargetType")


[docs]class ReadOnlyProperty(Generic[TargetType]): """ Descriptor for read-only properties (stored in xxx._name) """ def __init__(self, target_type: Type[TargetType]): super().__init__() def __set_name__(self, owner, name: str): self.name = f"_{name}" def __get__(self, instance: Any, *args, **kwargs) -> TargetType: if instance is None: # when accessed on class level rather than instance level return self # type:ignore return instance.__dict__[self.name] def __set__(self, instance: Any, value: Any): raise AttributeError("Property is for reading only, cannot assign to it.")
[docs]class WatchedProperty(Generic[TargetType]): """ Descriptor class for properties that are to be monitored for changes. Upon change of the value, the instance class invokes its `broadcast()` method to send the appropriate event notification to CentralDispatch Parameters ---------- target_type: type of watched property event: name of event to be triggered when property is changed inner_object_name: Used, e.g., in FullZeroPi where an inner-object property is to be set. attr_name: custom attribute name to be used (default: name from defining property in instance class, obtained in __set_name__ """ def __init__( self, target_type: Type[TargetType], event: str, inner_object_name: Optional[str] = None, attr_name: Optional[str] = None, fget=None, fset=None, ) -> None: self.event = event self.inner = inner_object_name self.attr_name = attr_name self.setter = fset self.getter = fget def __set_name__(self, owner, name: str) -> None: self.name = name self.attr_name = self.attr_name or name def __get__(self, instance: object, owner: Any) -> TargetType: if instance is None: # when accessed on class level rather than instance level return self # type:ignore assert self.attr_name if self.inner: inner_instance = instance.__dict__[self.inner] return getattr(inner_instance, self.attr_name) if self.getter is None: return instance.__dict__[ self.attr_name ] # cannot use getattr, otherwise recursion else: return self.getter(instance) def __set__(self, instance: DispatchClient, value: TargetType) -> None: if self.inner and self.attr_name: inner_instance = instance.__dict__[self.inner] setattr(inner_instance, self.attr_name, value) # Rely on inner_instance.attr_name to do the broadcasting. else: assert self.attr_name if ( self.attr_name not in instance.__dict__ and f"_{self.attr_name}" not in instance.__dict__ ): if self.setter is None: instance.__dict__[self.attr_name] = value else: self.setter(instance, value, name=self.attr_name) # Rely on inner_instance.attr_name to do the broadcasting. else: if self.setter is None: instance.__dict__[self.attr_name] = value else: self.setter(instance, value, name=self.attr_name) instance.broadcast(self.event)