Соглашение об именах Python для имени метода установки без аргументов - PullRequest
0 голосов
/ 17 января 2019

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

def set_log_paths(self):

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

Есть ли общепринятое слово в названии моего метода?

1 Ответ

0 голосов
/ 17 января 2019

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

...