0

I'm trying to write my own Python ANSI terminal colorizer, and eventialy came to the point where I want to define a bunch of public properties with similar body, so I want to ask: is there a way in Python to define multiple similar methods with slight difference between them?
Actual code I'm stuck with:

class ANSIColors:
    @classmethod
    def __wrap(cls, _code: str):
        return cls.PREFIX + _code + cls.POSTFIX

    @classproperty
    def reset(cls: Type['ANSIColors']):
        return cls.__wrap(cls.CODES['reset'])

    @classproperty
    def black(cls: Type['ANSIColors']):
        return cls.__wrap(cls.CODES['black'])
    ...

If it actually matters, here is the code for classproperty decorator:

def classproperty(func):
    return classmethod(property(func))

I will be happy to see answers with some Python-intended solutions, rather then code generation programs.

Edit 1: it will be great to preserve given properties names.

teviroff
  • 13
  • 3
  • 2
    I don't exactly understand the question, but I have a hunch that [functools.partial](https://docs.python.org/3/library/functools.html#functools.partial) may be one way to do it. – danielhoherd Jun 05 '22 at 00:44
  • I checked up your suggestion and thats not kinda thing I want to do. The thing is, in the above example there is two methods `reset` and `black`, and the only difference between them is the name and dictionary key, so my question is: is there a way to properly define a bunch of similar methods with different names and slight adjustments to the method body. Hope that helps. – teviroff Jun 05 '22 at 01:09
  • 1
    teviroff: I don't think you understand that `partial` would let you do that. – martineau Jun 05 '22 at 01:20
  • OK, I think I get it now, but that's still a lot of code, but I appreciate your solution. – teviroff Jun 05 '22 at 01:30
  • All you asked was how to define multiple similar methods with slight difference between them. Doing that without auto code generation seems intrinsically like it would require writing a fair amount of code if there are lots of methods/properties. – martineau Jun 05 '22 at 01:44
  • When I wrote this question, I thought about some solution that includes a simple cycle and manipulations with `self.__dict__` tbh(inspired by 5-line AttrDict implementation), but I'm not shure about this solution and won't try it, cause it seems like a magic to me. – teviroff Jun 05 '22 at 01:55
  • 1
    The usual way would be to parameterise the things that vary. In your simple case above, couldn't you just pass those strings as arguments to a common function? If not, why not? – ndc85430 Jun 05 '22 at 09:30
  • Of course that is the solution, but as I said, I wanted to make them all public properties, so it can be accessed by calling `ANSIColors.reset`. – teviroff Jun 05 '22 at 11:00
  • What 5-line `AttrDict` implementation are you referring to? – martineau Jun 05 '22 at 16:25
  • @martineau [Here it is](https://stackoverflow.com/a/14620633/19272888) – teviroff Jun 05 '22 at 17:25
  • Thanks — that's only 4-lines though. ;¬) Regardless, while I don't think that implementation is applicable, I also think I understand what you were getting at by mentioning it. – martineau Jun 05 '22 at 19:26

1 Answers1

0

I don't think you need (or really want) to use properties to do what you want to accomplish, which is good because doing so would require a lot of repetitive code if you have many entries in the class' CODES attribute (which I'm assuming is a dictionary mapping).

You could instead use __getattr__() to dynamically look up the strings associated with the names in the class' CODES attribute because then you wouldn't need to explicitly create a property for each of them. However in this case it needs to be applied it the class-of-the-class — in other words the class' metaclass.

The code below show how to define one that does this:

class ANSIColorsMeta(type):
    def __getattr__(cls, key):
        """Call (mangled) private class method __wrap() with the key's code."""
        return getattr(cls, f'_{cls.__name__}__wrap')(cls.CODES[key])


class ANSIColors(metaclass=ANSIColorsMeta):
    @classmethod
    def __wrap(cls, code: str):
        return cls.PREFIX + code + cls.POSTFIX

    PREFIX = '<prefix>'
    POSTFIX = '<postfix>'
    CODES = {'reset': '|reset|', 'black': '|black|'}


if __name__ == '__main__':

    print(ANSIColors.reset)  # -> <prefix>|reset|<postfix>
    print(ANSIColors.black)  # -> <prefix>|black|<postfix>
    print(ANSIColors.foobar)  # -> KeyError: 'foobar'

✶ It's important to also note that this could be made much faster by having the metaclass' __getattr__() assign the result of the lookup to an actual cls attribute, thereby avoiding the need to repeat the whole process if the same key is ever used again (because __getattr__() is only called when the default attribute access fails) effectively caching looked-up values and auto-optimizing itself based on how it's actually being used.

martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thanks a lot for your answer, this really helps. Honestly I think that this is the answer I wanted to see on such a topic. (^_^) – teviroff Jun 06 '22 at 11:56
  • That's good to hear. This is the result of you answering my questions and what I meant when I [commented](https://stackoverflow.com/questions/72503938/is-there-a-way-of-defining-multiple-methods-with-similar-bodies/72512678?noredirect=1#comment128090279_72503938) that I thought I got the gist of what you were trying/wanting to accomplish. Figuring *that* out was a big factor in it taking me so long to post an answer (the other was determining a good way of implement it). – martineau Jun 06 '22 at 12:14