-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extensions: decorators #97
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -338,31 +338,14 @@ def custom_handler_function(x): | |
|
||
|
||
# %% | ||
def wrap(func): | ||
""" | ||
|
||
Args: | ||
func: | ||
|
||
Returns: | ||
|
||
""" | ||
|
||
def pull_distributed_wrapper(x): | ||
""" | ||
|
||
Args: | ||
x: | ||
|
||
Returns: | ||
|
||
""" | ||
return func(x, tag="FWTW") | ||
from wsimod.extensions import extensions as extend | ||
|
||
return pull_distributed_wrapper | ||
|
||
@extend.node_attribute(obj=my_fwtw, attribute_name="pull_distributed") | ||
def new_distributed(pull_distributed, vqip): | ||
"""pull_distributed with the tag 'FWTW'.""" | ||
return pull_distributed(vqip, tag="FWTW") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Due to my lack of knowledge on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
what happens here is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, then what would be an example for replacing the attribute with a scalar value - just type the value below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't we just using |
||
my_fwtw.pull_distributed = wrap(my_fwtw.pull_distributed) | ||
|
||
# %% [markdown] | ||
# Explaining decorators is outside the scope of this tutorial, though you can | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"""Extensions module for decorators and subclasses. | ||
|
||
Example use for decorators: | ||
>>> from wsimod.extensions import extensions as extend | ||
>>> @extend.node_attribute(obj=my_fwtw, attribute_name="pull_distributed") | ||
>>> def new_distributed(pull_distributed, vqip): | ||
>>> return pull_distributed(vqip, tag="FWTW") | ||
""" | ||
|
||
|
||
def node_attribute(obj, attribute_name: str): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should accept a string indicating the node name to update, not a fully formed object. Of course, there has to be a way for the function to get the right object. I would suggest to create a singleton class - a sort of global variable, but a bit tidier -, from where this one pulls the information it needs. A minimal implementation would be something like: class ExtensionsHandler:
__slots__ = "_model"
_instance: Optional["ExtensionsHandler"] = None
def __new__(cls, model: Optional[Model] = None):
if not cls._instance:
if not model:
raise ValueError("The extension handler needs to be initialised with a model.")
cls._instance = super().__new__(cls)
cls._instance._model = model
return cls._instance
def __init__(self, model: Optional[Model] = None):
# Do not assign `model` here.
self._model: Model
def get_node(self, name: str) -> Node:
return self._model.nodes[name] Now, a few more things: In the class Model(...):
def __init__(self, ...):
ExtensionsHandler(self) And then, within the decorators, now taking a string as input, we do: def node_attribute(name: str, attribute_name: str):
def decorator(...):
obj = ExtensionsHandler().get_node(name)
... You can implement a more complex logic, add handlers for arcs, etc, but this would be the basic idea. In summary:
Does any of this makes sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does make sense - but what is wrong with just decorating the instatiated objects? the For me the slight benefit of having decorators that take objects is (as in the notebook example) they can be applied in a wider variety of situations than just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I explained above, if you take as input instantiated objects, you obviously need to already have access to the formed objects in order to use the decorator. That is the case in the tutorial since you are creating all the components programmatically, so you can pass the object directly, but it will not be true when including the extensions in an extension file. Loading that file in order to apply the extensions will fail as the instantiated object is not available in the module when loading it. And it needs to be if it is an input to the decorator itself. Just try to put your decorated function using instantiated objects in a separate file and load that file as a module. You will get something like |
||
""" | ||
Decorator to extend or modify a node attribute. | ||
|
||
Args: | ||
obj: The node object whose attribute should be modified. | ||
attribute_name (str): The name of the attribute to modify. | ||
|
||
Returns: | ||
A decorator function that takes the extension function as an argument. | ||
""" | ||
|
||
def decorator(func: callable): | ||
""" | ||
Decorator function that applies the extension function to the node attribute. | ||
|
||
Args: | ||
func (callable): The extension function that modifies the node attribute. | ||
""" | ||
attribute = getattr(obj, attribute_name) | ||
|
||
def wrapped_attribute(*args, **kwargs): | ||
return func(attribute, *args, **kwargs) | ||
|
||
setattr(obj, attribute_name, wrapped_attribute) | ||
return wrapped_attribute | ||
|
||
return decorator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid things need to be a bit more elaborate than this since for this approach to work, you need to already have access to the formed objects. This is the case in this tutorial since you are creating all the components programmatically so you can pass the object, but it will not be true when including the extensions in an extension file. Indeed, in this case it is not necessary to use decorators as you can simply do:
my_fwtw.pull_distributed = new_distributed
, and that's all.I suggest an alternative below.