简体   繁体   English

如何分组功能没有副作用?

[英]How to group functions without side effects?

I have a function with several helper functions. 我有一个带有几个辅助函数的函数。 That's fairly common case. 这是相当普遍的情况。 I want to group them in a common context for readability and I'm wondering how to do it right. 我想将它们分组在一个共同的上下文中以便于阅读,我想知道如何正确地做到这一点。

  • they take ~15 lines 他们需要~15行
  • only the main function is called from somewhere else 只有主函数从其他地方调用
  • no plans on reusing the helper functions in the near future 没有计划在不久的将来重用辅助功能

Simplified example: 简化示例:

def create_filled_template_in_temp(path, values_mapping):
    template_text = path.read_text()
    filled_template = _fill_template(template_text, values_mapping)
    result_path = _save_in_temp(filled_template)
    return result_path

def _fill_template(template_text, values_mapping):
    ...

def _save_in_temp(filled_template):
    _, pathname = tempfile.mkstemp(suffix='.ini', text=True)
    path = pathlib.Path(pathname)
    path.write_text(text)
    return path

...
create_filled_template_in_temp(path, values_mapping)

Please note that I don't want the helper methods on the module level because they belong to only one method. 请注意,我不希望模块级别的辅助方法,因为它们只属于一种方法。 Imagine having several such examples as above in the same module. 想象一下,在同一模块中有如上所述的几个这样的例子。 Maany non-public functions on module level. 模块级别的Maany非公共功能。 A mess (and this happens many times). 一团糟(这种情况多次发生)。 Also I'd like to give them context and use the context's name to simplify the naming inside. 另外,我想给它们上下文并使用上下文的名称来简化内部命名。

Solution #0: A module 解决方案#0:一个模块

Just put it in another module: 把它放在另一个模块中:

template_fillers.create_in_temp(path, values_mapping)

Problems: 问题:

  • that's too little code to add a file, especially when there are many files already (this creates a mess) 添加文件的代码太少,特别是当已经有很多文件时(这会造成混乱)
  • this is an action and now I'm forced to create a noun-based name for the module (or break the modules naming rule). 这是一个动作,现在我被迫为模块创建一个基于名词的名称(或打破模块命名规则)。 Moreover making it simple will make it too broad (in this case creating a set that really is a singleton). 而且简化它会使它太宽泛(在这种情况下创建一个真正是单身的集合)。

Finally this is just too little code to add a module for it. 最后,这只是为它添加模块的代码太少。

Solution #1: A class 解决方案#1:一堂课

Create a class with no __init__ and only one public (by naming convention) method: 创建一个没有__init__且只有一个公共(通过命名约定)方法的类:

class TemplateFillerIntoTemp:
    def run(self, path, values_mapping):
        template_text = path.read_text()
        filled_template = self._fill_template(template_text, values_mapping)
        result_path = self._save_in_temp(filled_template)
        return result_path

    def _fill_template(self, template_text, values_mapping):
        ...

    def _save_in_temp(self, filled_template):
        _, pathname = tempfile.mkstemp(suffix='.ini', text=True)
        path = pathlib.Path(pathname)
        path.write_text(text)
        return path

 ...
 TemplateFillerIntoTemp().run(path, values_mapping)

This is what I did many times in the past. 这就是我过去多次做过的事情。 Problems: 问题:

  • there are no side effects, so there's no need to have the class' instance 没有副作用,所以没有必要拥有类的实例
  • this is an action and now I'm forced to create a noun-based name for the class (or break the classes naming rule). 这是一个动作,现在我被迫为类创建一个基于名词的名称(或打破类命名规则)。 This leads to many of those "managers" or "creators". 这导致许多“管理者”或“创造者”。
  • this is a misuse of a class concept, this is just a little execution tree with a single function-interface, not a class of things. 这是对类概念的误用,这只是一个带有单个函数接口的小执行树,而不是一类东西。 Misusing concepts slows down understanding and may lead to further blending between uses. 滥用概念会降低理解速度,并可能导致使用之间进一步混合。 I know that in OOP this is common because in some languages you can't really make a function outside of a class, but this is too radical approach to order in code. 我知道在OOP中这很常见,因为在某些语言中你不能真正在类之外创建一个函数,但这对于代码中的命令来说太过激进了。 Objects are useful when they are the closest expression of your idea. 当对象最接近您的想法时,它们很有用。 This isn't the case. 事实并非如此。 Forcing not fitting order paradoxically generates disorder of a different kind :) 强迫不合适的顺序矛盾地产生不同类型的混乱:)

Solution #2: Static class 解决方案#2:静态类

Take solution #1, add @staticmethod everywhere. @staticmethod解决方案#1,到处添加@staticmethod Possibly also ABC metaclass. 可能还有ABC元类。

 TemplateFillerIntoTemp.run(path, values_mapping)

Pro: there is a clear indication that this all is instance-independent. 亲:有一个明确的迹象表明这一切都与实例无关。 Con: there's more code. Con:还有更多代码。

Solution #3: Class with a __call__ 解决方案#3:带有__call__的类

Take solution #1, create a __call__ function with the main method, then create on module level a single instance called create_filled_template_in_temp . 解决方案#1,使用main方法创建__call__函数,然后在模块级别创建一个名为create_filled_template_in_temp实例。

create_filled_template_in_temp(path, values_mapping)

Pro: calls like a single function. Pro:调用就像一个函数。 Con: implementation is overblown, not really fit for the purpose. 骗局:实施过于夸张,不适合此目的。

Solution #4: Insert helper functions into main function 解决方案#4:将辅助函数插入main函数

Add them inside. 将它们添加到里面

def create_filled_template_in_temp(path, values_mapping):
    def _fill_template(template_text, values_mapping):
        ...

    def _save_in_temp(filled_template):
        _, pathname = tempfile.mkstemp(suffix='.ini', text=True)
        path = pathlib.Path(pathname)
        path.write_text(text)
        return path

    template_text = path.read_text()
    filled_template = _fill_template(template_text, values_mapping)
    result_path = _save_in_temp(filled_template)
    return result_path

...
create_filled_template_in_temp(path, values_mapping)

Pro: this looks well if total number of lines is small and there are very few helper functions. 亲:如果总行数很少且辅助函数很少,这看起来很好。 Con: it doesn't otherwise. 骗局:否则没有。

Modification of #4: Make inner functions, and also have the function's body be an inner function. 修改#4:创建内部函数,并且函数体也是内部函数。 This has the nice property of still reading top-to-bottom, rather than having the body all the way at the bottom. 这具有仍然从上到下阅读的良好特性,而不是将身体一直放在底部。

def create_filled_template_in_temp(path, values_mapping):
    def body():
        template_text = path.read_text()
        filled_template = fill_template(template_text, values_mapping)
        result_path = save_in_temp(filled_template)
        return result_path

    def fill_template(template_text, values_mapping):
        ...

    def save_in_temp(filled_template):
        _, pathname = tempfile.mkstemp(suffix='.ini', text=True)
        path = pathlib.Path(pathname)
        path.write_text(text)
        return path

    return body()

(I don't care for the leading underscores, so they didn't survive.) (我不关心领先的下划线,所以他们没有活下来。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM