0

I have a method that draws a line or a patch of any shape I need given the name of the shape and some arguments, it works fine:

def draw_a_shape(ax, is_patch_not_line, shapename, **kwargs):
  ...

Now I need to have 30+ functions like draw_a_square_patch, draw_an_ellipse_outline etc. I can implement them in a trivial way, as below, but it's a bit painful:

def draw_a_square_patch(ax, **kwargs):
  draw_a_shape(ax=ax, is_patch_not_line=True, shapename='a_square', **kwargs)
def draw_a_square_outline(ax, **kwargs):
  draw_a_shape(ax=ax, is_patch_not_line=False, shapename='a_square', **kwargs)
def draw_an_ellipse_patch(ax, **kwargs):
  draw_a_shape(ax=ax, is_patch_not_line=True, shapename='an_ellipse', **kwargs)
def draw_an_ellipse_outline(ax, **kwargs):
  draw_a_shape(ax=ax, is_patch_not_line=False, shapename='an_ellipse', **kwargs)
.........

I wonder if there is a way to do it using a loop, and maybe something like setattr? I am adding an invalid code below, just to explain what I mean:

for shapename in ['a_circle', 'a_square', ...]:
  for is_patch_not_line in [False, True]:
    new_func_name = "draw_" + shapename + "_"
    if is_patch_not_line:
      new_func_name += "patch"
    else:
      new_func_name += "outline"
    global.setattr(new_func_name, "ax, **kwargs", ...) 
Yulia V
  • 3,507
  • 10
  • 31
  • 64
  • 1
    Does this answer your question? [Pretty way to define class functions that all call same function with different parameters](https://stackoverflow.com/questions/52994679/pretty-way-to-define-class-functions-that-all-call-same-function-with-different) – manveti Feb 16 '21 at 22:55
  • 1
    why do you need to have 30+ functions? you could just call `draw_a_shape` directly while varying the `shapename` argument and that seems perfectly readable to me – Josh Katofsky Feb 16 '21 at 22:57
  • @manveti - thanks but it does not, I think I have got to the level of that question more or less, but I wanted something like a loop – Yulia V Feb 16 '21 at 22:58
  • @JoshKatofsky - I am doing it for kids, I think it will be much easier for them. I can - and probably will - do a straightforward implementation, but a loop would be nicer. – Yulia V Feb 16 '21 at 22:59
  • Could you edit the question to clarify where your confusion is? If you understand `setattr` and `functools.partialmethod` it's hard to guess what more is left. – manveti Feb 16 '21 at 23:02
  • 2
    ah - yes that makes sense! this may be able to help: https://stackoverflow.com/a/14078558/6867216 – Josh Katofsky Feb 16 '21 at 23:03
  • @JoshKatofsky I have seen it, but it seems to give no control over arguments – Yulia V Feb 16 '21 at 23:06
  • @manveti edited. – Yulia V Feb 16 '21 at 23:07
  • 1
    Ah, I misread and thought these were class methods. Josh's dupe target about `globals()[func_name]` is probably better than mine. – manveti Feb 16 '21 at 23:22
  • @manveti it does not handle arguments, so prob not a dupe? – Yulia V Feb 16 '21 at 23:25

2 Answers2

5

functools.partial can be used to create partial objects, which act like some other function with some arguments already passed. These can be given dynamically-generated names in the local namespace via the locals() dictionary or in the module namespace via the globals() dictionary (with the standard caveats about dictionaries and other structures being favorable to globals() in most use cases aside from the specific one in this question).

Putting those together would give you something like:

for shapename in ['a_circle', 'a_square', ...]:
  for is_patch_not_line in [False, True]:
    new_func_name = "draw_" + shapename + "_"
    if is_patch_not_line:
      new_func_name += "patch"
    else:
      new_func_name += "outline"
    globals()[new_func_name] = functools.partial(
      draw_a_shape,
      shapename=shapename,
      is_patch_not_line=is_patch_not_line,
    )
manveti
  • 1,691
  • 2
  • 13
  • 16
1

The below seems to work. I had to create make_new_draw_a_shape to wrap the inner function because creating the inner function directly in the for loop was resulting in the last function generated assigned to all names.

def draw_a_shape(ax, is_patch_not_line, shapename, **kwargs):
    print(f"is_patch_not_line={is_patch_not_line}, shapename={shapename}")


def make_new_draw_a_shape(is_patch_not_line, shapename):
    def new_draw_a_shape(ax, **kwargs):
        draw_a_shape(ax, is_patch_not_line, shapename, **kwargs)
    return new_draw_a_shape


for shapename in ['a_circle', 'a_square', 'an_ellipse']:
    for is_patch_not_line in [False, True]:
        new_func_name = "draw_" + shapename + "_"
        if is_patch_not_line:
            new_func_name += "patch"
        else:
            new_func_name += "outline"
        globals()[new_func_name] = make_new_draw_a_shape(is_patch_not_line, shapename)


draw_a_circle_patch(None)
draw_a_circle_outline(None)
draw_a_square_patch(None)
draw_a_square_outline(None)
draw_an_ellipse_patch(None)
draw_an_ellipse_outline(None)

Output:

is_patch_not_line=True, shapename=a_circle
is_patch_not_line=False, shapename=a_circle
is_patch_not_line=True, shapename=a_square
is_patch_not_line=False, shapename=a_square
is_patch_not_line=True, shapename=an_ellipse
is_patch_not_line=False, shapename=an_ellipse
Czaporka
  • 2,190
  • 3
  • 10
  • 23