7

I was looking at the answer to this question: Is it possible to define a class constant inside an Enum?

What interested me most was the Constant class in Ethan Furman's answer.

class Constant:
 def __init__(self, value):
 self.value = value
 def __get__(self, *args):
 return self.value
 def __repr__(self):
 return '%s(%r)' % (self.__class__.__name__, self.value)

The question was about Python 3.4 but I'm using 2.7. In the answer Ethan sets the gravitational constant as an instance variable of the class Planet like so:

G = Constant(6.67300E-11)

My testing of this class in 2.7 shows that typing just G gives me this:

Out[49]: Constant(3)

(I set it to 3 for ease of use while testing. This looks like the __repr__ output to me, please correct me if I"m wrong.)

The value is available via G.value. However, in Ethan's answer he uses

return self.G * self.mass / (self.radius * self.radius)

This obviously only works if the value is returned vs the __repr__ output. Now if I change class Constant: to class Constant(int): then type G I still get the __repr__ output but if I type G * 4 I get 12 not the error I was getting. (TypeError: unsupported operand type(s) for *: 'instance' and 'int' )

So clearly something like the int object can output a number when called. Is there a magic method I'm missing that would allow me to do this for the Constant class? Since constants could be strings, integers, or floats I'd prefer to have 1 class that handles them all vs 3 separate classes that extend those objects.

The value is also settable via G.value. Can I lock this down so the Constant class behaves likes an actual constant? (I suspect the answer is no.)

asked Sep 26, 2014 at 20:37
5
  • Why do you want it to be constant? Commented Sep 27, 2014 at 5:48
  • @Veedrac Why wouldn't I want Constants? Or read only values within a class? From my reading on Python, I understand that it is counter to Python's "We're all consenting adults" philosophy. I really like Python so far but I think that philosophy is an oversight. I program for me, myself, and I - no one else will read it or use it likely. After coding in several languages for the past 15 years I've learned a few things. One of those things is that I can prevent days worth of debugging by simply designing a class where certain things just can't be changed outside of the internals of that class. Commented Sep 27, 2014 at 14:24
  • I was more wondering at what level you require it to be constant; my point was to check whether you were OK with a "constant through the public interface", which is the correct way of doing this, or you actually want it to be totally constant, which is impossible. Commented Sep 27, 2014 at 14:29
  • Also instead of caring whether they can set value, just make it private by naming it _value. Then you don't have to worry about p̶e̶o̶p̶l̶e̶ yourself changing it. Commented Sep 27, 2014 at 14:38
  • @Vedra Sorry my first couple of questions on SO regarding Python got polite responses of the form why would you want to do something so stupid? As far as constants are concerned i can live with them being read only via the public interface. I'd prefer they were true Constants and that i could write a class variable that was read only outside of the class that defines it but i understand these may not be possible in Python. Commented Sep 27, 2014 at 18:00

2 Answers 2

2

Your class Constant should inherit from object, to be a new style Python class.

In that way Constant will be a so called descriptor. In simple terms, descriptor are a Python construct to customize the behavior of getting and setting a class attribute. They are useful when an instance of a descriptor is set as an attribute of another class.

In your example Constant is the descriptor and Planet has an attribute which is an instance of Constant. When you get the attribute G of the Planet class (self.G in you example), what you really get is what is returned by the __get__ method of the descriptor, that is the value.

Note that __get__ is invoked only when the descriptor instance is accessed by another class attribute.

So, define the class like this:

class Constant(object):
 def __init__(self, value):
 self.value = value
 def __get__(self, *args):
 return self.value
 def __repr__(self):
 return '%s(%r)' % (self.__class__.__name__, self.value)

Then this little example:

c = Constant(3.14)
print c
class Test:
 c = Constant(3.14)
t = Test()
print t.c

Will print:

Constant(3.14)
3.14

See that when the Constant instance is printed directly, the method __repr__ will be called, but when printed as another class attribute, __get__ will be used.

You can read more on descriptors on this great article.

answered Sep 26, 2014 at 21:14
Sign up to request clarification or add additional context in comments.

1 Comment

From what I've tested it only works if it is a Class variable and not an Instance variable. If I move c = Constant to __init__ via self.c = Constant(...) then I get the __repr__ output. And it is still write-able which is fine for a constant since I can name with all CAPS. Ideally, I'm hoping to find a way to make an instance variable write-able from within the class but not to outside __main__ script. I had something in PHP that did this quite well but from what I've read so far it isn't possible in Python. The class attribute seems to always be available for overwrite. Thx
0

Well, value is a memeber of your class Constant; so you can try making it private:

class Constant:
 def __init__(self, value):
 # This actually transforms the variable to _Constant__value, 
 # but also hides it from outer scope
 self.__value = value
 def __get__(self, *args):
 # Altough the member is theorically renamed as _Constant__value,
 # it is completely accesible from inside the class as __value
 reurn self.__value
 def __repr__(self):
 return '%s(%r)' % (self.__class__.__name__, self.__value)

Another approach could be following this recipe.

Give them a try and let me now. Hope I'd helped you!

answered Sep 26, 2014 at 20:49

2 Comments

I haven't tried the recipe yet but the code you provided did not work for me as it's own instance. I couldn't get to the value directly anymore. I suspect it's because of what @baxeico said where it must become a descriptor within a class. Thx
The recipe appears to work. However, it must reside in a file and be imported. It also appears to be a singleton. Importing it within a class (just below the class definition) does not restrict it to that class's namespace. Nor does putting the import line in the __init__. The recipe works quite nicely if you can live with these limitations.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.