Соглашение об именах Python для имени метода установки без аргументов

Я писал код Python с классами, у которых будет метод, называемый что-то вроде:

def set_log_paths(self):

Дело в том, что этот метод не принимает аргумента, он определяет, какие значения должны быть основаны на других значениях себя. Неуместно ли в этом случае использовать слово "набор"? Я спрашиваю, потому что это не прямой получатель или установщик, как можно было бы использовать на языке с частными членами.

Есть ли в моем названии метода обычное слово?

1 ответ

Если вы не передаете никаких значений, а вместо этого вычисляете значение в момент вызова метода, основываясь на текущих значениях, разумно, чтобы глагол, описывающий действие, был "обновлен" - поэтому 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, чтобы он работал для вас, и заставить его вызывать методы обновления, чтобы их имя не имеет значения вообще!:-) (хорошо, этот код является излишним для заданного вопроса - но я был готов сделать что-то подобное перед сном сегодня вечером - отказ от ответственности: я не проверял связанные с наследованием угловые случаи, описанные здесь)

Другие вопросы по тегам