Reputation: 169
I have been writing python code with classes that will have a method called something like:
def set_log_paths(self):
The thing is, this method doesn't take an argument, it determines what some values should be based on other values of self. Is it inappropriate to use the word "set" in this case? I ask because it isn't a direct getter or a setter as one would use in a language with private members.
Is there a common conventional word to use in my method name?
Upvotes: 2
Views: 765
Reputation: 110486
f you don't pass any values, and instead, compute the value at the moment the method is called, based on current values, it is reasonable that the verb describing the action be "update" - therefore update_log_paths()
.
Just double check you really need this design, and what are the chances of you/other users of your class forgetting calling these "update" methods.
Python's introspection easily allows adopting some elements from "reactive programing", which could be used to trigger these updater methods when the values they depend upon are changed.
One optimal choice for such an architecture would be a descriptor for your properties that upon having __set__
called would check a class-level registry to "see" if events should be triggered, and then one decorator that would enable you to list the attributes that would trigger it. A base class with a proper __init_subclass__
method could set everything up.
Let's suppose you will have the "base properties" on your class as annotated attributes in the class body - the descritptor, decorator and base-class code for this to work could be something along:
from functools import wraps
from collections import ChainMap
class EventDescriptor:
def __init__(self, name, default):
self.name = name
self.default = default
def __get__(self, instance, owner):
if not instance:
return self
return instance.__dict__[self.name] if self.name in instance.__dict__ else self.default
def __set__(self, instance, value):
instance.__dict__[self.name] = value
triggers = instance._post_change_registry.get(self.name, [])
for trigger in triggers:
getattr(instance, trigger)()
def triggered_by(*args):
def decorator(func):
func._triggered_by = args
return func
return decorator
class EventPropertyMixin:
def __init_subclass__(cls, **kw):
super.__init_subclass__(**kw)
for property_name, type_ in cls.__annotations__.items():
if not hasattr(cls, property_name):
raise TypeError("Properties without default values not supported in this example code")
# It would also be trivial to implement runtime type-checking in this point (and on the descriptor code)
setattr(cls, property_name, EventDescriptor(property_name, getattr(cls, property_name)))
# collects all registries in ancestor-classes, preserving order:
post_change_registry = ChainMap()
for ancestor in cls.__mro__[:0:-1]:
if hasattr(ancestor, "_post_change_registry"):
post_change_registry = post_change_registy.new_child(ancestor._post_change_registry)
post_change_registry = post_change_registry.new_child({})
for method_name, method in cls.__dict__.items():
if callable(method) and hasattr(method, "_triggered_by"):
for property_name in method._triggered_by:
triggers = post_change_registry.setdefault(property_name, [])
if method_name not in triggers:
triggers.append(method_name)
cls._post_change_registry = post_change_registry
class Test(EventPropertyMixin):
path1: str = ""
path2: str = ""
@triggered_by("path1", "path2")
def update_log_paths(self):
self.log_paths = self.path1 + self.path2
And let's this working:
In [2]: t = Test()
In [3]: t.path1 = "/tmp"
In [4]: t.path2 = "/inner"
In [5]: t.log_paths
Out[5]: '/tmp/inner'
So, this is complicated code, but code that usually would lie inside a framework, or in base utility libraries - with these 50 lines of code, you could be using Python to work for you, and have it call the updating methods, so their name won't matter at all! :-) (ok, this code is way overkill for the question asked - but I was in a mood to produce something like this before sleeping tonight - disclaimer: I had not tested the inheritance-related corner cases covered in here)
Upvotes: 1