Imposing typesystem on a class#

Properties#

It is often necessary to allow only certain values to be assigned to an attribute of a class. That can be achieved using properties. For instance, if we want to implement class Square and allow only positive values for the square edge length a, the implementation would be:

[1]:
class Square:
    def __init__(self, a):
        self.a = a

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        if value <= 0:
            raise ValueError("Edge length must be positive (a>0).")
        else:
            self._a = value

Now, if we attempt to use an invalid value to set the edge length a, an exception will be raised.

[2]:
s = Square(a=5)  # Instantiate the class with correct attribute value

try:
    s.a = -3
except ValueError:
    print("Exception raised.")
Exception raised.

Imposing typesystem using ubermagutil.typesystem#

In large projects with a large number of classes, a lot of input checks have to be performed. This makes the code grow and it causes a lot of code repetition. An example of the ubermagutil usage for the previously shown Square class is

[3]:
import ubermagutil.typesystem as ts


@ts.typesystem(a=ts.Scalar(positive=True))
class Square:
    def __init__(self, a):
        self.a = a


s = Square(a=5)

If we try to set an invalid value:

[4]:
try:
    s.a = -3
except ValueError:
    print("Exception raised.")
Exception raised.

Similarly, if we want to define MyClass with the following attributes:

  • a - an unsigned integer

  • b - a three-dimensional vector

  • c - one of the allowed values from the set {'left', 'right'}

  • d - variable of type list

  • name - a valid Python variable name

[5]:
@ts.typesystem(
    a=ts.Scalar(expected_type=int, unsigned=True),
    b=ts.Vector(size=3),
    c=ts.Subset(sample_set={"left", "right"}, unpack=False),
    d=ts.Typed(expected_type=list),
    name=ts.Name(),
)
class MyClass:
    def __init__(self, a, b, c, d, name):
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.name = name

Now, we can attempt passing invalid values expect them to be rejected by the imposed typesystem.

[6]:
mc = MyClass(
    a=5, b=(1, 2, 3), c="right", d=[1, 2, "abc"], name="myclass"
)  # valid initialisation

# Set mc.a with float
try:
    mc.a = 3.14
except TypeError:
    print("mc.a: Exception raised.")

# Set mc.b with a two-dimensional vector
try:
    mc.b = (10, 11)
except ValueError:
    print("mc.b: Exception raised.")

# Set mc.c with an invalid value of the string
try:
    mc.c = "down"
except ValueError:
    print("mc.c: Exception raised.")

# Set mc.c with an invalid value of tuple
try:
    mc.d = (1, 2, 3, 4)
except TypeError:
    print("mc.d: Exception raised.")

# Set mc.name with an invalid Python variable name
try:
    mc.name = "Nikola Tesla"  # contains spaces
except ValueError:
    print("mc.name: Exception raised.")
mc.a: Exception raised.
mc.b: Exception raised.
mc.c: Exception raised.
mc.d: Exception raised.
mc.name: Exception raised.

Deleting an attribute#

Deleting an attribute is never allowed and an AttributeError is raised.

[7]:
# Attempt to delete an attribute.
try:
    del mc.a
except AttributeError:
    print("Exception raised.")
Exception raised.