Skip to content

Following The DRY Principle #4

@DecisioNaut

Description

@DecisioNaut

Tired of repeating yourself when using the TM1Service resource, @onefloid?
I think I remember a conversation we had about this issue...

As Raymond Hettinger uses to shout: "There must be a better way!"
So I just remembered decorators and asked Mr Copilot!

Here's what I got:

Question:
How can I use a decorator for avoiding repeated with statements for using resources?

Answer:
You can create a decorator that handles the opening and closing of resources, such as files, to avoid repeated with statements. Here's an example of how you can modify your code to use such a decorator:

def resource_manager(resource_path):
    def decorator(func):
        def wrapper(*args, **kwargs):
            with open(resource_path) as resource:
                return func(resource, *args, **kwargs)
        return wrapper
    return decorator


@resource_manager('./game/test.py')
def reader(resource):
    return resource.read()


def main():
    print(reader())


if __name__ == '__main__':
    main()

In this example, the resource_manager decorator takes a resource_path argument and opens the resource (in this case, a file) within the wrapper function. The reader function now takes the opened resource as an argument, allowing you to avoid repeating the with statement.

Caution

However, my dear @onefloid, using this naive approach, you won't like it. Now, reader doesn't really know it's input data or types in your code editor anymore. But there's the functools-library coming to your rescue! And so let's ask Copilot again:

Question:
How can I improve this approach using functools?

Answer:
You can use functools.wraps to preserve the metadata of the original function when creating decorators. Here's how you can improve your approach using functools:

import functools

def resource_manager(resource_path):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            with open(resource_path) as resource:
                return func(resource, *args, **kwargs)
        return wrapper
    return decorator


@resource_manager('./game/test.py')
def reader(resource):
    return resource.read()


def main():
    print(reader())


if __name__ == '__main__':
    main()

In this improved version, the resource_manager decorator uses functools.wraps to wrap the func function. This ensures that the metadata of the original function (such as its name and docstring) is preserved. The reader function now takes the opened resource as an argument, allowing you to avoid repeating the with statement.

Hope, this was what you were looking for... At least I think this would be pretty neat.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions