0

I'm creating a program making a "to do list". I prepared a function which writes a list of items entered by user. Then, using decorator, I want every item of the list to be added to txt file. I struggle with writing items of the lists one by one to the file. Using for loop in wrapper function doesn't work - nothing is shown on the log after running

def decorator(func):
    def wrapper(*args):
        with open("to_do_list.txt", "a") as l:
            for i in range(len(args)):
                l.write(f"{func(*args[i])} \n")

    return wrapper

@decorator
def to_do():
    print("TO DO LIST")
    status = True
    list = []
    while status:
        x = input("Add a task to a list: ")
        list.append(x)
        q = input("Do you wanna continue typing (Y), or quit (Q)?").lower()
        if q == "n":
            status = False
    return list

to_do()
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • `to_do` doesn't take any arguments. – Barmar Aug 23 '22 at 19:45
  • And you're not calling it with any arguments. What do you expect to be written to the file? – Barmar Aug 23 '22 at 19:45
  • Get out of the habit of using `for index in range(len(list)):`. Use `for item in list:` or `for index, item in enumerate(list):` – Barmar Aug 23 '22 at 19:45
  • 2
    Why are you doing this with a decorator? Decorators are wrappers that you're going to want to put around different functions. This seems very specific to the `to_do()` function, you should just do it in the function itself. – Barmar Aug 23 '22 at 19:47
  • Are you expecting it to write the list that's returned by the function? `args` are the arguments, not the value. – Barmar Aug 23 '22 at 19:49
  • BTW, Don't use `list` as a variable name. It's the name of a built-in class. – Barmar Aug 23 '22 at 19:49
  • thank you for tips. Im new to programming and I wanted to create something using decorators (i'm learning it today). You're right, using decorators in this case is overcomplicate – Kamil Gryboś Aug 23 '22 at 20:10

2 Answers2

0

This shouldn't be done with a decorator. That redefines the function, and should only be used when you have a general modification that you'll want to make to a number of different functions. You can no longer call the base function directly, so it shouldn't be used when the base function is usable by itself.

Insted, define an ordinary function that writes a list to the file, then call that with the returned value.

def write_list_to_file(l, filename):
    with open(filename, 'a') as f:
        for item in l:
            f.write(item + '\n'))

write_list_to_file(to_do(), "to_do_list.txt")

If you really want to do this with a decorator, you have to process the result of the function, not the arguments. And to make the decorator more general, it should take the filename as a parameter, not hard-code it. See Decorators with parameters?

def append_file(filename):
    def decorator(func)
        def wrapper(*args, **kwargs):
            results = func(*args, **kwargs)
            with open(filename, 'a') as f:
                for item in results:
                    f.write(f'{item}\n')
            return results
        return wrapper
    return decorator

@append_file("to_do_list.txt")
def to_do():
    print("TO DO LIST")
    to_do_list = []
    while True:
        x = input("Add a task to a list: ")
        to_do_list.append(x)
        q = input("Do you wanna continue typing (Y), or quit (Q)?").lower()
        if q == "n":
            break
    return to_do_list

to_do()
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • You say this shouldn't be done using a decorator, but then (almost) literally create a decorator? – Robin Dillen Aug 23 '22 at 20:05
  • 1
    The difference is that this doesn't modify `to_do()`. You can still call it without writing to the file. – Barmar Aug 23 '22 at 20:07
  • He did not. This is a regular, plain function, used to do a regular, plain task. Decorators are fancy stuff to solve fancy problems. It is not like _"haha, @decorator go brrr"_ – Rodrigo Rodrigues Aug 23 '22 at 20:09
  • decorators are not fancy stuff, they're syntactic sugar for f(g()) (where f is a function). I don't see an issue with using a feature of a language. – Robin Dillen Aug 23 '22 at 20:11
  • @RobinDillen The main difference is that a decorator is a permanent redefinition of the function. It allows you to add some feature to the function, they're useful when this is a common addition that you'd want to add to many different functions. – Barmar Aug 23 '22 at 20:14
  • I agree but since not enough context is known you can't state that this shouldn't be done with a decorator. – Robin Dillen Aug 23 '22 at 20:17
  • @RobinDillen, newcomers should be busy getting familiar with writing down good logic and algorithms, and using the right tool for the right jobs. The decorator feature here is adding nothing to the solution, and getting him distracted from getting things done. There are other problems out there that are better use cases for decorators, and when OP gets to that bridge, he will cross. – Rodrigo Rodrigues Aug 23 '22 at 20:18
  • educating by not giving an explanation as to why decorators shouldn't be used and in general just ignoring a topic is not a great idea in my opinion. Learning is nog a straight line (first this, than that). Maybe OP will get more confused but OP will learn. – Robin Dillen Aug 23 '22 at 20:24
  • OK, I've shown how to do it with a decorator. – Barmar Aug 23 '22 at 20:36
  • Your first code works properly. We do not call to_do() function, but pass only as an argument to write_list_to_file function. Does it mean, that passing function to another function (without decorators) call it automatically in all cases? – Kamil Gryboś Aug 23 '22 at 20:46
  • @KamilGryboś A function call like `f(g())` will first evaluate `g()`. This involves calling the function `g()` and replacing the `g()` part of `f(g())` with the return value (`x`), making `f(x)`. This is then passed to `f()`, and the result of that your final result. – Nathaniel Ford Aug 23 '22 at 20:58
0

If you want to fix the generator:

def decorator(func):
    def wrapper(*args, **kwargs):  #  use args and kwargs
        res = func(*args, **kwargs)
        with open("to_do_list.txt", "a") as l:
            # no need to iterate over the arguments
            l.write(f"{res} \n")
        return res
    return wrapper

@decorator
def to_do():
    print("TO DO LIST")
    status = True
    list = []
    while status:
        x = input("Add a task to a list: ")
        list.append(x)
        q = input("Do you wanna continue typing (Y), or quit (Q)?").lower()
        if q == "n":
            status = False
    return list

to_do()

You should ask yourself if a decorator is what you want to use, since as mentioned in the comments under Barmar's answer, you wouldn't be able to not execute this just function (you could add parameters to the decorator, but as said, ask yourself is this is the easiest solution).

Robin Dillen
  • 704
  • 5
  • 11
  • 1
    Not sure it's great practice to leave a file open for the length of a function call that may loop indefinitely waiting on user input. Probably move the `open` to after the function call, if you have to do it this way? (Another reason a decorator is not the best fit for this problem.) – Nathaniel Ford Aug 23 '22 at 20:23
  • Didn't notice the while loop, thx! – Robin Dillen Aug 23 '22 at 20:25