5

I'm trying to use Thespian (https://thespianpy.com/doc/), a Python library for the actor model, and in particular I'm trying to use the "troupe" functionality. As I understand it, the troupe decorator acts as a scheduler to run multiple actors up to the max_count specified, with each actor running in parallel. The troupe functionality is applied as a decorator on my actor class:

@troupe(max_count = 4, idle_count = 2)
class Calculation(ActorTypeDispatcher):
    def receiveMsg_CalcMsg(self, msg, sender):
        self.send(sender, long_process(msg.index, msg.value, msg.status_cb))

I would like to configure max_count at run time, instead of design time. I'll admit my base knowledge on decorators is weak.

How can I pass a value to max_count at run time?

I have gone through these, but I'm still in the dark:

Does python allow me to pass dynamic variables to a decorator at runtime?

http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/

Per the answers so far, I attempted this, but the decorator was not being applied (i.e. it acted as if there were no decorator). I commented out the @troupe implementation above the class, that method (including with the variable) is working fine. This approach isn't:

# @troupe(max_count=cores, idle_count=2)
class Calculation(ActorTypeDispatcher):
    def receiveMsg_CalcMsg(self, msg, sender):
        self.send(sender, long_process(msg.index, msg.value, msg.status_cb))

def calculate(asys, calc_values, status_cb):
    decorated_class = troupe(max_count=5, idle_count=2)(Calculation)
    calc_actor = asys.createActor(decorated_class)

There is other stuff in the calculate function, but that is pretty much just some book keeping.

Brian
  • 642
  • 7
  • 18

2 Answers2

6

Decorator syntax is just a shortcut for applying a function to a class. You can make that function call yourself once you know the value for max_count.

class Calculation(ActorTypeDispatcher):
    ...

# Time passes

c = input("Max count: ")
Calculation = troupe(max_count=int(c), idle_count=2)(Calculation)

(or, simply wait until you do have c before defining Calculation, as shown by @brunns.)

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Oh, I remember seeing this format of decorator application, but your illustration is very concrete and useful. I didn't think I had access to the called class, but I do: `calc_actor = asys.createActor(Calculation)` – Brian Apr 15 '19 at 14:10
  • Oh, I just noticed something and not sure if it is intentional...does the class name need to be the same on the left and right? In my code I said `decorated_class= troupe()(Calculation)`. Must it be as you illustrated, with `Calculation = troupe()(Calculation)` ? When I do that I get `UnboundLocalError: local variable 'Calculation' referenced before assignment`. And yes, my Calculation class is defined before the call I attempt to make. – Brian Apr 15 '19 at 14:28
  • You can use different names; perhaps you want both the original class *and* the class created by the decorator. If you are calling `troupe` in *another* function, the the fact that you assign to `Calculation` makes that a local variable *anywhere* in the function, so the argument to the decorator *isn't* the global name that refers to your original class. If that's the case, you might need to use a `global` statement to make sure the *global* variable is bound to the new class, or you do need to use a different name. – chepner Apr 15 '19 at 14:43
  • `troupe` is defined in a module that I imported. My `Calculation` class is defined immediately above the function where I'm trying to use this method to apply the decorator. The code at the end of my modified question doesn't work, but if I omit `Calculation` for example, the decorator complains that it wasn't provided. Is it possible that this decorator doesn't support this method of application? – Brian Apr 15 '19 at 14:49
  • Another thing: all the example and text I see refers to decorating a function rather than a class, but in the example code I was adapting, the @troupe() decorator was before the class (which works) but when I try the function calling approach in this answer, it doesn't work. – Brian Apr 15 '19 at 14:57
  • You are going to have to provide more context; you are calling `troupe` in a different scope than where `Calculation` was originally defined? Keep in mind that the code in your modified question doesn't change the original class at all. – chepner Apr 15 '19 at 14:57
  • I'll strip down my code and add to the question. But when I do `decorated_class = troupe()(Calculation)` what is `decorated_class`? My understanding is that a decorator is a function that returns another function. However, this makes it appear that the decorator would be a function that takes a class and returns another function...I'll post my code – Brian Apr 15 '19 at 15:01
  • `decorated_class` is a reference to some class, either a new class based on the old one, or the original one itself, if `troupe` simply does some work with the given class rather than defining a new one. – chepner Apr 15 '19 at 15:05
  • This is the definition of the troupe() decorator: https://github.com/kquick/Thespian/blob/d295738fcb6189c77cac1d517f325b9674145e55/thespian/troupe.py#L187 . It seems very different from what is presented here: https://www.codementor.io/sheena/advanced-use-python-decorators-class-function-du107nxsv – Brian Apr 15 '19 at 15:10
  • Okay, thank you for confirming that. I just came to that conclusion as well. The example code is here: https://github.com/kquick/Thespian/blob/d295738fcb6189c77cac1d517f325b9674145e55/examples/fibtroupe/fibactor.py#L43 I'll move ahead with the other way to apply the decorator, since that seems to still work with the class. To completely understand then: a decorator is a function that operates on a function to return a function. A decorator can also be a function that operates on a CLASS and returns a CLASS. It sounds like perhaps this is not applied correctly? – Brian Apr 15 '19 at 15:17
  • 1
    I deleted my previous comment; `troupe` returns a function that basically take a class, and replaces that class's `receiveMessage` method with a new method that wraps the old one. So in this case, you *do* get a reference to the original class which *has* been modified. (At this point, I don't know enough about the `thespian` library to say anything intelligible about how it works.) – chepner Apr 15 '19 at 15:20
  • Okay, I'll process this, and see what else I can come up with. – Brian Apr 15 '19 at 15:23
  • I'm accepting the other answer since that is what works with this particular module. I appreciate you helping me understand your initial suggestion and diagnosis. – Brian Apr 15 '19 at 17:43
4

Should be as simple as:


my_max = get_max_from_config_or_wherever()

@troupe(max_count = my_max, idle_count = 2)
class Calculation(ActorTypeDispatcher):
    ...

Thing to remember is that class and def statements are themselves executed.

brunns
  • 2,689
  • 1
  • 13
  • 24
  • I guess my_max would need to be a global or module variable of some kind? – Brian Apr 15 '19 at 14:10
  • A module variable would be fine, yes - but you could inline the function call, too. – brunns Apr 15 '19 at 14:13
  • `@troupe(max_count=get_max_from_config_or_wherever(), idle_count=2)` would work. – brunns Apr 15 '19 at 14:13
  • I guess these all have to do with ensuring you define the variable before or as the decorator is called, and I had trouble picturing that. Very helpful, thank you. – Brian Apr 15 '19 at 14:15
  • This approach works well and I tried it out. I'll play around with how I can to inject that value. I can't get the other answer to work. – Brian Apr 15 '19 at 14:35
  • 2
    In my experimentation, the value of `my_max` is evaluated at import time, rather than at run time...is there any way to change this behavior so that I can change the value of my_max at run time? – Brian Apr 15 '19 at 18:05
  • Strictly speaking, there is no separate run time - but clearly you mean post-import. That's going to depend on the Thespian library, with which I'm not familiar, sorry. – brunns Apr 15 '19 at 18:58