On Tue, May 10, 2022 at 1:20 PM Paul Moore <p.f.moore@gmail.com> wrote:
On Tue, 10 May 2022 at 16:31, Barney Stratford
<barney_stratford@fastmail.fm> wrote:
>
> Hello all.
>
> It occurs to me that creating threads in Python is more awkward than it needs to be. Every time I want to start a new thread, I end up writing the same thing over and over again:
>
> def target(*args, **kwds):
>     ...
> t = threading.Thread(target = target, args = <something>, kwargs= <something>)
> t.start()
>
> This becomes especially repetitive when calling a target function that only makes sense when run in a new thread, such as a timer.
>
> In my own code, I’ve taken to decorating functions that always run in new threads. Then I can call the function using the usual function call syntax, and have the new thread returned to me. With the decorator, the code reads instead:
>
> @threaded
> def target(*args, **kwds):
>     …
> t = target(*args, **kwds)
>
> This has a couple of advantages. I don’t have to import the threading module all over my code. I can use the neater syntax of function calls. The function’s definition makes it clear it’s returning a new thread since it’s decorated. It gets the plumbing out of the way so I can concentrate more on what my code does and less in how it does it.
>
> It feels like the right place for this decorator is the standard library, so I’ve created PR #91878 for it. @rhettinger suggests that this is a bit premature, and that we should discuss it here first. Thoughts?

I like this. I'd probably use it if it existed. Your example isn't
particularly compelling, though - calling the function "target" makes
sense when it's the target of the thread, but not so much when it's
the thread itself. Could you give a motivating example based on real
code, rather than just the "this is what the syntax would look like"
as you did above? Ideally showing what it would look like with and
without the decorator?

Things I *think* i get, but I'd like to see clearly in an example, include:

1. Does t = target(...) start the thread? I think it does.
2. Is it possible to create daemon threads? @threaded(daemon=True)
seems plausible. I suspect your PR doesn't have this, but it wouldn't
be difficult to add. But I don't actually know if it's worth adding,
TBH.
3. Can you create multiple threads for the same function? I assume t1,
t2, t3 = target(arg1), target(arg2), target(arg3) would work.

I'm probably being dense here - I'm pretty sure your proposal actually
is as simple as it looks (which is a good thing!) and I'm just
over-complicating things in my head.

Things that come to mind that should be considered here:
1) not every two liner function should be added to the stdlib
and, on the other hand, 
2) asyncio features `loop.run_in_executor`
which does essentially the same, with more care for what happens
in "real world" code, but is only usable in async code. 

all in all it can be useful for quick and dirty code - and maybe
something more elaborate allowing control on whether to create 
daemon threads, or maybe something that will deal with concurrent.future.executors
as well, and allow access to the function call results 

All in all, I find it a nice snippet, but one that is not aligned with
the trend Python has been taking over the last years, 
facilitating the writing of "quick and dirty" scripts in contrast
with application code that will take care of all possible corner-cases.

As a good example of what I am talking about, we've seen the contrary
 movement take place, long ago, when
"os.popen" was superseded by "subprocess.*": I think up to today people
had not catched up with the far more complicated API of the latter. 

All in all, I'd eventually use this "@threaded" if it was there. 

 
Paul

   js
  -><-