将函数或属性应用于同一类的多个方法

Posted

技术标签:

【中文标题】将函数或属性应用于同一类的多个方法【英文标题】:Apply function or property to multiple methods of same class 【发布时间】:2021-11-28 11:25:30 【问题描述】:

在以下示例中,MyExample 类读取文件名中包含下划线的文件名列表。

class MyExample(object):
    def __init__(self, location: str):
        self.location = location

    def get_file_names(self):
        # For simplicity, lets just return an example of the output
        return ["my_file_name_01.txt", "my_file_name_02.txt"]

    def get_file_name_number(self):
        # Idem as before. 
        return ["file_name_01.txt", "file_name_02.txt"]

    def get_file_size(self):
        # It does not return a list of strings so the desired 
        # property or function will not be applied here
        return [3800, 4000]

代码执行时,结果是

my_object = MyExample("./a/random/path")
print(my_object.get_file_names())
print(my_object.get_file_name_number())
print(my_object.get_file_size())

# The results:
['my_file_name_01.txt', 'my_file_name_02.txt']
['file_name_01.txt', 'file_name_02.txt']
[3800, 4000]

现在,我想找到一种应用函数或属性(我们称之为to_dot)的方法,它可以替换get_file_names() 和@987654326 输出中的点的下划线@。 最终代码应该返回类似

# Calling this function with .to_dots
my_object.get_file_names().to_dots
["my.file.name.01.txt", "my.file.name.02.txt"] # <-- The desired output

# Calling this function with .to_dots
my_object.get_file_name_number().to_dots
["file.name.01.txt", "file.name.02.txt"] # <-- The desired output

# Calling this function with .to_dots
my_object.get_file_name_number().to_dots
AttributeError #  # <-- The desired output ... or something similar

有没有办法将to_dots 添加到MyExample 类中,以替换里面某些方法的下划线? 我对装饰器不是很熟悉,但我怀疑可能有一个技巧可以做到这一点。到目前为止,我没有成功尝试使用@property,但据我所知,有很多类型的装饰器......或者也许它可以在没有装饰器的情况下完成,我很迷茫。 谢谢。

【问题讨论】:

您的问题是如何编写一个用点替换下划线的函数,或者如何将任何函数应用于列表中的所有项目,或者如何编写装饰器来转换方法的返回值有什么功能吗? @mkrieger1。我知道使用 str.replace 我可以将 _ 更改为 . 并且使用理解列表(或许多其他方式)我可以将替换应用于所有项目。我的问题是如何向类中的某些方法添加函数。谢谢。 我会让to_dots 成为一个接受列表的独立函数。然后像这样调用它:to_dots(my_object.get_file_names()) 这更灵活,因为现在您可以在任何您想要的列表对象上使用它。不仅仅是这个类返回的。 【参考方案1】:

你可以这样做:

from collections import UserList

class MyList(UserList):
    @property
    def to_dots(self):
        return [s.replace("_", ".") for s in self.data]

class MyExample(object):
    def __init__(self, location: str):
        self.location = location

    def get_file_names(self):
        return MyList(["my_file_name_01.txt", "my_file_name_02.txt"])

结果

my_object = MyExample("./a/random/path")
print(my_object.get_file_names())
print(my_object.get_file_names().to_dots)

['my_file_name_01.txt', 'my_file_name_02.txt']
['my.file.name.01.txt', 'my.file.name.02.txt']

【讨论】:

谢谢@Timus。我已经接受这个答案是正确的,因为这个答案与帖子的原始问题更相关,但是,在考虑了两次之后,根据您对列表功能的评论,我不会在我的代码中使用这种方法。我意识到避免使用诸如 MyList 之类的类可能是一种更好的做法,因为它不会返回真正的列表。也许这是一个值得商榷的观点,但我认为更好的做法(或更 Pythonic)是使用 trincot 建议的方法,这并不意味着创建更多类。 @eliasmaxil 我同意,我可能也不会使用这种方法 - 取决于更广泛的背景。我应该在答案中明确表示,这只是为了展示您如何在更狭义的意义上实现您所要求的(绝不是一个好的回答方式)。无论如何,有趣的问题!【参考方案2】:

您可以使用类装饰器函数装饰器应用于指定的类方法。

from functools import wraps
import inspect


def dec_methods(decorator, *members):
    """Class decorator to apply specfied decorator to specified members of class."""

    def dec_the_class(cls):
        for name, m in inspect.getmembers(cls, inspect.isfunction):
            if name in members:
                setattr(cls, name, decorator(m))
        return cls
    return dec_the_class


def to_dots(func):
    """Function decorator to replace '_' with dots in list of strings returned."""

    @wraps(func)
    def wrapped(*args, **kwargs):
        results = func(*args, **kwargs)
        results = [result.replace('_', '.') for result in results]
        return results
    return wrapped


@dec_methods(to_dots, 'get_file_names', 'get_file_name_number')
class MyExample(object):
    def __init__(self, location: str):
        self.location = location

    def get_file_names(self):
        # For simplicity, lets just return an example of the output
        return ["my_file_name_01.txt", "my_file_name_02.txt"]

    def get_file_name_number(self):
        # Idem as before.
        return ["file_name_01.txt", "file_name_02.txt"]

    def get_file_size(self):
        # It does not return a list of strings so the desired
        # property or function will not be applied here
        return [3800, 4000]


my_object = MyExample("./a/random/path")
print(my_object.get_file_names())        # -> ['my.file.name.01.txt', 'my.file.name.02.txt']
print(my_object.get_file_name_number())  # -> ['file.name.01.txt', 'file.name.02.txt']
print(my_object.get_file_size())         # -> [3800, 4000]

【讨论】:

谢谢@martineau,这个答案也有效。我刚刚投票给了另一个需要更少代码来实现相同结果的人。 它更短,因为它在做不同的事情。在我的回答中,类的方法经过修饰,因此它们会自动执行您想要的操作 - 即您不需要像在另一个答案中那样自己手动调用 to_dots() 的结果。另一个答案也是改变方法返回的值的类型(尽管它是list 的子类)。 是的,你是对的,代码传递的是列表而不是列表的子类,这有助于避免代码其他部分的错误。【参考方案3】:

为什么不将所需的输出格式(点或不点)作为可选参数传递给这些方法?

class MyExample(object):
    def __init__(self, location: str):
        self.location = location

    @staticmethod
    def map_list(lst, dotted=False):
        return [s.replace("_", ".") for s in lst] if dotted else lst

    def get_file_names(self, dotted=False):
        return self.map_list(["my_file_name_01.txt", "my_file_name_02.txt"], dotted)

    def get_file_name_number(self, dotted=False):
        return self.map_list(["file_name_01.txt", "file_name_02.txt"], dotted)

    def get_file_size(self):
        return [3800, 4000]

my_object = MyExample("./a/random/path")
print(my_object.get_file_names())  # Underscores untouched
print(my_object.get_file_name_number(dotted=True))  # Underscores replaced
print(my_object.get_file_size())

【讨论】:

谢谢@trincot。我必须承认这是最简单的解决方案,我明确地在代码中使用它。我喜欢它,因为这样我将避免创建更多的类并进行更多的导入,此外它只会在原始代码中添加几行。但是,由于最初的答案是如何将函数应用于类的不同方法,所以我必须投票给与原始问题更相关的答案。但是对于这个答案的其他读者,我建议给这个答案更多的投票。【参考方案4】:

定义你自己的装饰器:

def to_dots(func):
    def inner():
        original_return = func()
        return [item.replace('_','.') for item in original_return ]
    return inner

并将其用作:

@to_dots
def get_file_names(self):

【讨论】:

以上是关于将函数或属性应用于同一类的多个方法的主要内容,如果未能解决你的问题,请参考以下文章

在 Interface Builder 中实例化同一类的多个对象会导致共享属性

同一个类函数的多个定义取决于同一个继承类的多个类型?

将多个过滤器应用于 pandas DataFrame 或 Series 的有效方法

构造函数、ECMAscript(ES6)

Ember.Object 中的多个属性或单个观察者

部分类静态类Object类ToString()方法扩展方法的使用