0

I am looking to facilitate the following behavior, which is NOT just to get a string representation of the class! The following is a SIMPLIFIED version of my problem:

>>> Alphabet.a
'a'
>>> Alphabet.b
'b'
>>> Alphabet.alpha
'α'
>>> Alphabet.a.is_vowel
True

I currently have the following setup. The main reason I chose this over any other approach is that the data I am accessing has to, at most, be set on start-up and that this approach is able to make use of my IDEs code completion feature.

class Character:
    def __init__(self, c: str, v: bool):
        self.name = c
        self.is_vowel = v

class Alphabet:
    a: Character = Character('a', True)
    b: Character = Character('b', False)
    c: Character = Character('c', False)

That already allows me to call Alphabet.a.name to get the name, however I'd like to be able to only call Alphabet.a to get the name parameter, while still being able to access Alphabet.a.is_vowel.

  • 1
    What do do you want `type(Alphabet.a)` to be? – wim Feb 01 '23 at 19:54
  • 1
    You can't make `Alphabet.a` be an actual string, and also have `Alphabet.a.is_vowel` work, because actual strings don't have an attribute `is_vowel`. So at best `Alphabet.a` can be a custom class, and its repr can be customised to look like a string. – deceze Feb 01 '23 at 19:57
  • @deceze it is a duplicate, you just had the wrong canonical (the titles are a bit misleading). I'll see what I can do about cleaning it up later. – Karl Knechtel Feb 01 '23 at 19:58
  • @deceze You can, actually. It's not pretty, but it is possible. – wim Feb 01 '23 at 19:59
  • I was hoping for there to be some sort of magic function akin to `def __self__(): return self` that can be overridden to return something else, while still allowing me to access the other parameters. – UmBottesWillen Feb 01 '23 at 20:01
  • @wim I guess you can subclass `str`, yes, but that's really not the solution you would usually want to go for… – deceze Feb 01 '23 at 20:01
  • @wim I'd very much like to know how. – UmBottesWillen Feb 01 '23 at 20:01
  • It is also possible without using inheritance / subclass. But it's pretty black magic, and it's not clear why you can't simply add `__str__` and `is_vowel` methods on your `Character` type, that seems like the obvious solution here. – wim Feb 01 '23 at 20:06
  • @wim In this example the main attribute I'm interested in is a string, but if I find an acceptable solution to this problem, I'm going to have different types be the primary type that ought to be returned if only the class instance is called. – UmBottesWillen Feb 01 '23 at 20:19
  • 1
    "some sort of magic function akin to def __self__(): return self that can be overridden to return something else" That's what `__str__` and `__repr__` are for. Unless you actually need to get a string as a result, rather than simply controlling how instances are displayed. But in that case, it's considerably more complex: `Alphabet.a.name` **must** mean the same thing as `(Alphabet.a).name`, so if `Alphabet.a` *actually evaluates to a string*, it would have to evaluate to a *subtype of* `str` in order to apply a `name` attribute. – Karl Knechtel Feb 01 '23 at 20:20
  • ... Okay, I tried it and this is *doable*, but I really doubt it's really necessary or desirable. But I won't object to a further re-opening. I'll even write an answer. – Karl Knechtel Feb 01 '23 at 20:24
  • "I'm going to have different types be the primary type that ought to be returned if only the class instance is called" This... isn't comprehensible. I think you misunderstand the meaning of the terminology. – Karl Knechtel Feb 01 '23 at 20:26
  • @KarlKnechtel I worded that poorly. In this example, the type of the parameter to be returned when the class instance is called is a string, so `a = Character('a', True)`, `type(a) # type: str`. But I don't intend to have the type of the parameter returned to always be `str`. – UmBottesWillen Feb 01 '23 at 20:32
  • The closest answer to what I want to achieve is this one: https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-subobjects-chained-properties But it doesn#t work with code completion, as the path through the nested parameters is given as a string. – UmBottesWillen Feb 01 '23 at 20:38
  • 1
    It's possible to make `Character('a', True)` create a `str` unconditionally, but then a) there's no point to the class, since no instances of it will be created; b) the `str` can't have an `is_vowel` attribute, as it's a built-in type which doesn't allow for setting additional attributes. Working around that requires having a subtype of `str` instead of `str` itself. – Karl Knechtel Feb 01 '23 at 20:42
  • @KarlKnechtel Again, it is possible without sub-classing, by controlling where "is_vowel" gets looked up. Though, such an approach gets to some pretty dark corners of CPython and I'm still waiting for an answer about what `type(Alphabet.a)` is expected to be. – wim Feb 01 '23 at 20:47
  • @wim As far as I can forsee for now, the type of `Alphabet.a` ought to be the type of `a.name` in this example. And as I said earlier, the `type` of `a.name` won't always be `str` – UmBottesWillen Feb 01 '23 at 20:52
  • @wim I'm afraid I simply can't fathom what you propose. It's not as if you can monkey-patch `str.__getattr__`. – Karl Knechtel Feb 01 '23 at 20:53
  • You cannot simultaneously have `Alphabet.a` provide a `str` and at the same time have `Alphabet.a.some_other_attribute` work since then `Alphabet.a` *cannot be a string* (ok, no sane way) – juanpa.arrivillaga Feb 01 '23 at 21:40
  • 1
    And this isn't about strings specifically, `X.y` should be some type, but then that type must have a `.z` attribute if you want to be able to have `X.y.z`. Anything else is fundamentally broken. Or fundamentally requires some black magic (AST re-writing, live call stack modification, dynamic code modification, etc etc). What is fundamentally broken is the *abstraction you've designed*. Thnk of it this way, python sees `X.y.z` not as `X.(y.z)` but as `(X.y).z` – juanpa.arrivillaga Feb 01 '23 at 21:43

0 Answers0