User Guide¶
The most commonly used decorators in touketsu are
immutable() and nondynamic().
immutable() makes class instances immutable, while
nondynamic() makes class instances nondynamic, i.e.
existing attributes may be modified, but no new attributes can be created
dynamically at runtime. For example, suppose we have a class my_class, where
class my_class:
"""This is my class.
:param a: First parameter.
:param b: Second parameter.
"""
def __init__(self, a = 1, b = 2):
self.a = a
self.b = b
Typically, it is possible to do something like in the interpreter:
>>> a = my_class()
>>> a.d = 14
>>> a.d
14
If my_class was decorated with nondynamic(), then an
AttributeError would be raised, although an assignment to an existing
attribute like a.a = 13 would be allowed.
Note that immutable() and nondynamic()
modify the docstrings of the classes they modify. Respectively, they prepend
"[Immutable] " and "[Nondynamic] ", sans double quotes, to the docstring
of the decorated class. If no changes to the docstring are desired, use
identity_immutable() and
identity_nondynamic() instead.
A simple example¶
Using the decorators is very simple. Suppose we have a class a_class defined
as 1
class a_class:
"""A sample class.
:param a: The first parameter.
"""
def __init__(self, a = "a"):
self.a = a
If we wanted instances of a_class to allow modification of instance
attributes but not to allow dynamic instance attribution creation, we would use
the nondynamic() decorator as follows:
from touketsu import nondynamic
@nondynamic
class a_class:
"""A sample class.
:param a: The first parameter.
"""
def __init__(self, a = "a"):
self.a = a
If we then make an instance of a_class named aa, we would be able to
modify aa.a, but attempting aa.aaa = 15 or a similar operation would
result in an AttributeError.
Note that the a_class docstring has now been modified into
"""**[Nondynamic]** A sample class.
:param a: The first parameter.
"""
Sphinx would be able to properly read this docstring and generate formatted documentation.
Inheritance¶
When using decorators like this that disable the ability of Python class
instances to dynamically create new instance attributes, we run into trouble
with inheritance. Fortunately, using touketsu decorators requires minimal
changes to existing code in order to preserve multiple inheritance.
Let’s define a second class b_class as follows:
from touketsu import immutable
@immutable
class b_class:
def __init__(self, b = "b"):
self.b = b
Suppose we also have classes c_class and A_class defined as
class c_class(a_class, b_class):
def __init__(self, a = "aa", b = "bb", c = "cc"):
a_class.__init__(self, a = a)
b_class.__init__(self, b = b)
self.c = c
class A_class(a_class):
def __init__(self, a = "A", aa = "AA"):
super().__init__(a = a)
self.aa = aa
Now, suppose that a_class was decorated with
nondynamic(). Which of these two classes’
__init__() methods would raise an AttributeError when called?
As you may have expected, both, as the bound and unbound __init__() have
been decorated already. Fortunately, touketsu provides the
orig_init() function to wrap unbound __init__()
methods, returning the original class __init__(). Therefore, if we define
c_class as
from touketsu import orig_init
class c_class(a_class, b_class):
def __init__(self, a = "aa", b = "bb", c = "cc"):
orig_init(a_class.__init__)(self, a = a)
orig_init(b_class.__init__)(self, b = b)
self.c = c
Now no AttributeError will be thrown when c_class() is executed.
Note that although a_class is decorated with
nondynamic() and b_class is decorated with
immutable(), c_class is just a normal class. We can
then in turn decorate c_class if we want to.
However, the situation is different if the subclass does not override the
superclass __init__() method. For example, suppose we defined a class
d_class as
import random
class d_class(a_class, b_class):
def random_touch_ab(self):
self.a = random.random()
self.b = random.random()
If we called its mro() 2 method to get the method resolution
order, we would see
>>> d_class.mro()
[__main__.d_class,
__main__.a_class,
__main__.b_class,
object]
The upshot is that the __init__() and __setattr__() methods of
d_class instances are from a_class, and d_class inherits the
nondynamic property of a_class, which was decorated with
nondynamic(). Note that d_class instances also do not
have the b instance attribute, since that attribute is defined in the
__init__() method of b_class. Therefore, the following sequence of
calls will end in an AttributeError saying that d is a nondynamic
class instance.
>>> d = d_class()
>>> d.random_touch_ab()
Our problem is easy to solve, however. To make d_class instances normal
class instances, we can simply use the urt_class()
decorator, which will remove the restriction and make d_class a normal
Python class, as its __init__() method will be the original
__init__() method of a_class, per the method resolution order. The
definition of d_class would then look like this:
import random
from touketsu import urt_class
@urt_class
class d_class(a_class, b_class):
def random_touch_ab(self):
self.a = random.random()
self.b = random.random()
Note that if we had overriden the a_class __init__() in the definition
of d_class, then we would not need the urt_class()
decorator used above.
- 2
This only works in Python 3 or in Python 2 with new-style classes.
Class and instance methods¶
It remains to address how class and instance methods are treated in classes
decorated by touketsu class decorators like
nondynamic(). For example, we may have a class
some_class that is defined as
class some_class:
def __init__(self, a = "a", b = "b"):
self.a = a
self.b = b
@classmethod
def special_class_method(cls):
cls.aa = 1000
return cls(a = "A")
def method_one(self, val):
self.aaa = val
def method_two(self):
if hasattr(self, aa) and hasattr(self, aaa):
return 2
if hasattr(self, aa):
return 0
elif hasattr(self, aaa):
return 1
return -1
Suppose we want class instances to be immutable. We cannot just decorate
some_class with immutable(), since method_one
attempts to create a new instance attribute, which will cause an
AttributeError to be raised upon call. Instead, we would define
some_class as follows:
from touketsu import immutable, urt_method
@immutable
class some_class:
def __init__(self, a = "a", b = "b"):
self.a = a
self.b = b
@classmethod
def special_class_method(cls):
cls.aa = 1000
return cls(a = "A")
@urt_method
def method_one(self, val):
self.aaa = val
def method_two(self):
if hasattr(self, aa) and hasattr(self, aaa):
return 2
if hasattr(self, aa):
return 0
elif hasattr(self, aaa):
return 1
return -1
Note that we do not need to decorate the classmethod()
special_class_method with urt_method(), as touketsu
restrictions only affect the instances of a class, not the class itself.
method_two also does not need to be decorated with
urt_method() since it does not create or modify instance
attributes.