4

When assigning attributes to objects, I often find that I have to change attributes that are dependent upon others, say, light and darkness. Here's an example:

class shade:
    def __init__(self, light):
        self.light=light
        self.darkness=100-light

    def __str__(self):
        return (str(self.light) +  ',' + str(self.darkness))



>>> shade1=shade(30,70)
>>> shade1.light
30
>>> shade1.darkness
70

Now, while that is cool and all, what I would like is the same process to occur, but within the same object if a change in one attribute occurs. If I reset the property of light, I want darkness to incr/decr accordingly. I can do this with a function if it works to change the property of light, returning the new value of light/darkness, but I'd like a way to do this if I change the property of light simply by re-assigning its value, without the use of functions. Like this:

>>> shade1.light=50
>>> shade1.light
50
>>> shade1.darkness
50
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
Jim Jam
  • 713
  • 4
  • 10
  • 22
  • First of all. your class wont work. contractor has only one argument, and you put two when you make the class `shade(30,70)` – Marcin Dec 05 '14 at 04:10
  • I know, it was intentional, the absence of light is darkness this is 100% dependent on the intensity of light, so I thought it be to set the intensity of light and presume the intensity of darkness. It works out fine if you run it by the way. – Jim Jam Dec 05 '14 at 04:17
  • 1
    His point is it doesn't, the constructor is defined to take one argument but you are passing two. – tripleee Dec 05 '14 at 04:23

1 Answers1

18

Define darkness as a property

class shade:
    def __init__(self, light):
        self.light = light

    @property
    def darkness(self):
        return 100 - self.light

    def __str__(self):
        return '{},{}'.format(self.light, self.darkness)

Properties outwardly appear as attributes, but internally act as function calls. When you say s.darkness it will call the function you've provided for its property. This allows you to only maintain one variable internally.

If you want to be able to modify it by assigning to darkness, add a setter for the property

class shade:
    def __init__(self, light):
        self.light = light

    @property
    def darkness(self):
        return 100 - self.light

    @darkness.setter
    def darkness(self, value):
        self.light = 100 - value

Thereby actually modifying light. If you've never seen properties before, I'd recommend throwing in some print()s to the bodies of the functions so you can see when they are called.

>>> s = shade(70)
>>> s.light
70
>>> s.darkness
30
>>> s.light = 10
>>> s.darkness
90
>>> s.darkness = 20
>>> s.light
80
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • 1
    Fantastic! I knew this feature had to exist. Thanks very much Ryan! – Jim Jam Dec 05 '14 at 04:25
  • @user2901512 You are welcome. FYI, in languages without this feature (Java, C++, etc) it would be done by having a private int for light, and accessing both via method calls, like `s.darkness()` and `s.light()`, adding a level of indirection – Ryan Haining Dec 05 '14 at 04:27
  • Just to nitpick, supplying a non-integer value for `light` (e.g. 'foo', [], None, -999.) will be silently accepted by `__init__()`, and will only trigger an Exception whenever you subsequently access `darkness`, or call `__str__()`. You may want to typecheck `light` inside `__init__()` – smci Dec 05 '14 at 04:29
  • Thanks for the heads up Ryan! – Jim Jam Dec 05 '14 at 04:37
  • Hey smci, by typecheck, do you mean raising an attribute error any time a non-int is assigned? – Jim Jam Dec 05 '14 at 04:38
  • @user2901512 I can't really recommend that, type checking is generally considered to be an anti-pattern in python. As you've pointed out one would need a check in every set of darkness and light to be consistent. If you need static types, use a statically typed language. – Ryan Haining Dec 05 '14 at 23:35
  • @user2901512 if you were to check, then the most correct exception would be a `TypeError`. But you really probably shouldn't – Ryan Haining Dec 06 '14 at 00:02