если вы не передаете никаких значений, а вместо этого вычисляете значение в момент вызова метода, основываясь на текущих значениях, разумно, чтобы глагол, описывающий действие, был «обновлен» - поэтому update_log_paths()
.
Просто дважды проверьте, действительно ли вам нужен этот дизайн, и каковы шансы того, что вы / другие пользователи вашего класса забудут вызывать эти методы «обновления».
Самоанализ Python позволяет легко принять некоторые элементы из " реактивного программирования ", которые можно использовать для запуска этих методов обновления при изменении значений, от которых они зависят.
Одним оптимальным выбором для такой архитектуры будет дескриптор для ваших свойств, который при вызове __set__
будет проверять реестр уровня класса, чтобы «увидеть», должны ли события запускаться, и затем один декоратор, который позволит вам перечислите атрибуты, которые будут вызывать его. Базовый класс с правильным методом __init_subclass__
может все настроить.
Предположим, что у вас будут «базовые свойства» в вашем классе в качестве аннотированных атрибутов в теле класса - дескриптор, декоратор и код базового класса, для работы которых может быть что-то вроде:
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
И давайте это сработает:
In [2]: t = Test()
In [3]: t.path1 = "/tmp"
In [4]: t.path2 = "/inner"
In [5]: t.log_paths
Out[5]: '/tmp/inner'
Итак, это сложный код, но код, который обычно находится внутри фреймворка или в базовых служебных библиотеках - с этими 50 строками кода вы могли бы использовать Python для работы для вас, и вызовите методы обновления, чтобы их имя не имело никакого значения! :-)
(хорошо, этот код является излишним для заданного вопроса - но у меня было настроение сделать что-то подобное перед сном сегодня вечером - отказ от ответственности: я не проверял связанные с наследованием угловые случаи, описанные здесь)