Class Decoration¶
Both techniques,
Observable Decoration
and
Observer Decoration,
are static, in the sense, decorations are done e.g. in @
-notation evaluated
at compile time. They are applied to static functions.
Decoration of a class by default addresses decoration of the class initializer, this means
@Observable
class Dog:
def __init__(self):
pass # Some code ...
should be understood as
class Dog:
@Observable
def __init__(self):
pass # Some code ...
But this behavior is insidious, e.g.
from decoratory.observer import Observable
class Person:
def __init__(self, name: str = "Jane Doe"):
print(f"{name} arrived.")
@Observable(observers=Person)
class Dog:
def __init__(self, name: str = "Teddy"):
print(f"Dog {name} arrived.")
# Case 1: Dog is an observable to Person
prs = Person() # Jane Doe arrived.
dog = Dog() # Dog Teddy arrived.
# Jane Doe arrived.
The instantiation of Dog
induced an instantiation of Person
.
Warning
Class decoration — Take care when decorating a class initializer
Notifying the __init__ method of an observer results in a new instance! This means calling the observable induces instantiation of a new observer object, surely in not any case this is the desired behavior …
So the decoration should not address a class but one (or more) target
methods of the class. As already mentioned, this is easy if this callback
function is a @staticmethod
or @classmethod
.
class Person:
def __init__(self, name: str = "Jane Doe"):
print(f"{name} arrived.")
@staticmethod
def action1(act: str = "Hello?"):
print(f"Person says {act}")
@classmethod
def action2(cls, act: str = "What's up?"):
print(f"Person says {act}")
@Observable(observers=[Person.action1, Person.action2])
class Dog:
def __init__(self, name: str = "Teddy"):
print(f"Dog {name} arrived.")
# Case 2: Dog is an observable to Person.action
prs = Person() # Jane Doe arrived.
dog = Dog() # Dog Teddy arrived.
# Person says Hello?
# Person says What's up?
This is how it usually works: one action of the observable, here it’s
the instantiation of Dog
, triggers one to many actions at each observer,
here the two actions of Person
.
But often an instance method is also interesting as a callback function:
If a particular instance
prs = Person(name="John Doe")
of a person is meant, a decoration like@Observable(observers=prs.action)
with the instance method can be applied toDog
.For an arbitrary instance of a person
@Observable(observers=Person().action)
works.
Even a list of F
structures would be possible to optionally submit
different parameters.
from decoratory.observer import Observable
from decoratory.basic import F
class Person:
def __init__(self, name: str = "Jane Doe"):
self.name = name
print(f"{name} arrived.")
def action(self, act: str = "Hello?"):
print(f"{self.name} says {act}")
prs1 = Person() # Jane Doe arrived.
prs2 = Person("John Doe") # John Doe arrived.
@Observable(observers=[prs1.action, F(prs2.action, "What's up?")])
class Dog:
def __init__(self, name: str = "Teddy"):
print(f"Dog {name} arrived.")
# Case 3: Dog is an observable to actions of various person instances.
dog = Dog() # Dog Teddy arrived.
# Jane Doe says Hello?
# John Doe says What's up?
Here, one action of the observable, the instantiation of Dog
, triggers
one to many actions at each selected resp. instantiated (but not generally at
each!) observer of Person
. In such situations, a late dynamic decoration
Pyc. 1 could be a good idea.
So far, instantiating Dog
resulted in an information and induced
action at Person
. If Dog
has its own actions that need to be
selectively monitored, each of the selected actions can of course be decorated
individually as an Observable
. For the sake of a better overview, this
can also be done on the class itself.
class Person:
def __init__(self, name: str = "Jane Doe"):
self.name = name
print(f"{name} arrived.")
@classmethod
def actionA(cls, act: str = "Hello?"):
print(f"Person says {act}")
def actionB(self, act: str = "Hello?"):
print(f"{self.name} says {act}")
@Observable(methods=["action1", "action2"],
observers=[Person.actionA, Person("Any Doe").actionB])
class Dog:
def __init__(self, name: str = "Teddy"):
self.name = name
print(f"Dog {name} arrived.")
@staticmethod
def action1(act: str = "Woof!"):
print(f"Dog acts {act}")
def action2(self, act: str = "Brrr!"):
print(f"{self.name} acts {act}")
# Case 4: Dog is an observable with selected actions.
# Any Doe arrived.
prs = Person() # Jane Doe arrived.
dog = Dog() # Dog Teddy arrived.
dog.action1() # Dog acts Woof! (@staticmethod)
# Person says Hello? (@classmethod)
# Any Doe says Hello? (Instance 'Any')
Dog.action2(dog) # Teddy acts Brrr! (Instance 'Teddy')
# Person says Hello? (@classmethod)
# Any Doe says Hello? (Instance 'Any')
The last line Dog.action2(dog)
provides the instance of Teddy
as the
first argument, self
. This works because internally the class method
Dog.action2
was registered instead of an instance method that didn’t
exist at compile time. On the other hand, the call dog.action2()
fails because this instance method was not registered. But, if this is what
is to be achieved, an instance method can first be created and registered,
just as seen above in Class Decoration, Case 3.
© Copyright 2020-, Martin Abel, eVation. All Rights Reserved. |