0

I would like to create a class that, when I call without the double brackets, returns a value in any context, just like what str or int does. For example, what can I do to make this possible:

>>> class color:
...     def __init__(self, r, g, b):
...         self.r = r
...         self.g = g
...         self.b = b
...     def whatever_it_is(self):
...         return (self.r, self.g, self.b) # Class returns tuple
>>> myColor = color(16, 32, 64)
>>> myColor.g
32
>>> myColor
(16, 32, 64)
>>> color(5, 68, 37)
(5, 68, 37)

My goal is to create a color class compatible with Pygame. For example, I could do mySurface.fill(color(16, 32, 64)), which would be the same as mySurface.fill((16, 32, 64)). My color class is like pygame.math.Vector2, but written in Python and not C.

As well as this, I have a bonus question that doesn't need to be answered: How do I make a class that can't be constructed?

Is this possible in Python 3.9?

Final edit: The answer below does not work fully. Here is what I mean:

>>> myColor = color(1, 2, 3)
>>> myColor
(1, 2, 3)
>>> myColor.g
2
>>> myColor.g = 4
>>> myColor
(1, 2, 3)
>>> myColor.g
4

I have found out about the __iter__ magic method and my highest priority was Pygame compatibility by converting a class to a tuple, so... ¯\_(ツ)_/¯

BlueStaggo
  • 171
  • 1
  • 12
  • Does [this](https://stackoverflow.com/a/37639615/14913991) help you ? – limserhane Jan 10 '21 at 12:41
  • **How do I make a class that can't be constructed**, what do you mean by this? You can just not define a constructor (`__init__` method) – Countour-Integral Jan 10 '21 at 12:41
  • @limserhane I want to call the class like `thisClass` to get a value without conversion using `tuple()` and such. – BlueStaggo Jan 10 '21 at 12:43
  • @Countour-Integral I don't want to do this `myClass()` and then do stuff like `aClass.thisProperty = thisValue`. – BlueStaggo Jan 10 '21 at 12:44
  • 3
    You could make your class an *extension* of tuple, or in this case just use the existing [`namedtuple`](https://docs.python.org/3/library/collections.html#collections.namedtuple) in `collections`. Or make your class a [sequence](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence), like tuples are. But there's no magic method for what you're asking because how would you distinguish between when you're supposed to be accessing the instance and when the value it wraps? – jonrsharpe Jan 10 '21 at 12:45
  • So you want to use **methods** of a class (or just variables) without creating an instance of it? – Countour-Integral Jan 10 '21 at 12:45
  • @TomKarzes no, because then it might *look like* a tuple but wouldn't be accepted by a method that accepts one. – jonrsharpe Jan 10 '21 at 12:48
  • 1
    Why do you want to create a class that works as a tuple if you can simply use tuples? mycol = (16,43,200) and then use mycol ... you can create it as constant somewhere and use it ... creating a class simply to have a class makes no sense to me? – Patrick Artner Jan 10 '21 at 12:49
  • @jonrsharpe Then how can I construct a class like `a(b, c, d)` instead of `a((b, c, d))` from inheriting a tuple or add methods to a namedtuple? – BlueStaggo Jan 10 '21 at 12:50
  • @PatrickArtner I would like to add methods to it. – BlueStaggo Jan 10 '21 at 12:50
  • You can inherit from a namedtuple, too; the docs show it. – jonrsharpe Jan 10 '21 at 12:50
  • @BlueStaggo About your "final edit": That's because `tuple`s are immutable. You can similary inherit from `list` and achieve what you want. Asking a question and after receiving an answer, editing it to ask *another* question is not how this site works. The answer was covering all the requirements that are mentioned in the question. – Asocia Jan 13 '21 at 06:45

1 Answers1

3

You want your class to be act like a tuple but you also want to add some custom attributes to reach color codes (i.e myColor.g). In this case you can subclass tuple and write a custom __new__:

class color(tuple):
    def __new__ (cls, r, g, b):
        # Here we are saying that initialize a new tuple with
        # arguments we provided. tuple expects one parameter
        # so we are sending r, g, b as one parameter: (r, g, b)
        return super(color, cls).__new__(cls, (r, g, b))

    def __init__(self, r, g, b):
        # Then we can add whatever attribute we like to our tuple.
        self.r = r
        self.g = g
        self.b = b

>>> myColor = color(16, 32, 64)
>>> myColor.g
32
>>> myColor
(16, 32, 64)
>>> myColor == (16, 32, 64)
True

As suggested by kaya3 in the comments, the same thing can be achieved by using namedtuples:

>>> from collections import namedtuple
>>> color = namedtuple('color', ('r', 'g', 'b'))
>>> myColor = color(16, 32, 64)
>>> myColor.g
32
>>> myColor
color(r=16, g=32, b=64)
>>> myColor == (16, 32, 64)
True
Asocia
  • 5,935
  • 2
  • 21
  • 46
  • Thank you so much! I wish you could explain what is happening in the __new__ method. I do not know much about how super() works. – BlueStaggo Jan 10 '21 at 12:55
  • @BlueStaggo You can check the edit. I hope it's more clear now. – Asocia Jan 10 '21 at 13:01
  • 2
    This is a longwinded way of creating a `collections.namedtuple`. – kaya3 Jan 10 '21 at 14:37
  • @kaya3 I didn't know `namedtuple`s can do this. I'll edit the post to mention about it. Thanks! – Asocia Jan 10 '21 at 18:10
  • Slightly shorter way: `namedtuple('color', 'r g b')` since the constructor accepts a string of space-separated attribute names. – kaya3 Jan 10 '21 at 18:36