1

I'm not really sure what a good title for this question would be, so i'll explain what I want to do.

I want to have a class, which can be instantiated in some way like so:

class Setting(int):
  def __init__(self, value, description=None):
    super(Setting, self).__init__(value)
    self.description = description

max_memory = Setting(5, description="The maximum memory we can use")

print s + 5 # 10
print s.description # The maximum memory we can use

But then, I'd like this to work for all objects, so I can just have one class. So that I can have the object behave exactly as if it were the same as the underlying value object, but just with the additional description attribute.

I tried changing the above to:

class Setting(object):
  def __init__(self, value, description=None):
    type(value).__init__(value)
    self.description = description

s = Setting(5)

print s + 5
print s.description

But now I get the error:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    print s + 5
TypeError: unsupported operand type(s) for +: 'Setting' and 'int'

and if I look at dir(self), it does not seem to have run the initialiser for the class of value.

Is it possible to do this?

I know i could just make a few different classes for the different types, but i'd rather only have the one and just have it be generic.

(yes i realise that if the object has a description attribute it would be overwritten, i plan on using this only for the primitives, which is one of the reasons it's a problem in the first place since i can't just add attributes.)

arco444
  • 22,002
  • 12
  • 63
  • 67
will
  • 10,260
  • 6
  • 46
  • 69
  • Perhaps you could use a single `descriptions` dict, whose keys are primitive instances, and whose values are strings that describe each instance. – Kevin Jan 05 '15 at 16:09
  • Because you are not using the value parameter passed to the method anywhere in the class , you can implement `__add__` method and return the value accordingly. – ZdaR Jan 05 '15 at 16:11
  • Is this an accurate paraphrasing of your question? 'I want to create a class that accepts two inputs, one being a number object of any standard numerical value type (int, float, long etc), and the other being a text. The object created should have all the methods and behaviors of the numerical value type that was inputed as the first argument, as well as supporting the attribute reference `.description`, which returns the value of the second input. Is this possible, and if so how?' – zehnpaard Jan 05 '15 at 16:37
  • @zehnpaard: yes, but with the small change that i'm not considering only numeric types, but any type. – will Jan 05 '15 at 16:44

3 Answers3

5

Here's a proof-of-concept of what I think you're trying to accomplish:

def Setting(value, description=None):
  class Internal(type(value)):
      def __new__(self, value, description=None):
          return type(value).__new__(Internal, value)
      def __init__(self, value, description=None):
          super(Internal, self).__init__(value)
          self.description = description
  return Internal(value, description)

s = Setting(5, description="The maximum memory we can use")

print s+10, s.description

v = Setting([1, 2], description="A useful list")

print v+[3], v.description

which emits

15 The maximum memory we can use
[1, 2, 3] A useful list

The core idea is to wrap the class into a factory function -- an "extra level of indirectness" that should help you achieve your desired result.

I call this "a proof of concept", first of all, because it's quite wasteful of memory: a new class object springs up for every call to Setting. The factory function should hold a dict acting as a registry from type(value) to the specific Internal class wrapping it, populating it on the fly -- a typical "memoization" idiom, though here it's used to save memory, not so much running time.

Second, I haven't verified that all special methods behave as desired on a Setting-wrapped "primitive type" (for all "primitive types" of interest, both mutable and immutable) -- offhand it seems like it should work, but nothing but thorough unit-testing can give you confidence here!-)

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • I realised i could do this just a few minutes ago, and was just in the process of testing it out. Thanks. (i even called my internal class `_internal` – will Jan 05 '15 at 16:43
  • @will, always glad to help -- if you need help with the memoization idiom, I can edit this answer to show how to use it, of course. – Alex Martelli Jan 05 '15 at 16:48
  • The memoization comment isn't a problem for me, as i'm only going to have about 30ish settings, I just wanted to have them all stroed together, and the idea of having to add a `[0/1]` everywhere i wanted to grab the appropriate part from a list of pairs i thought would make things ugly. – will Jan 05 '15 at 16:48
  • This is a great answer. It also illustrates the differences between new and init. Upvoted! To clarify a bit, __new__ returns the instance of the class, so it doesnt have to also initialize it, it could also be: def __new__(self, value, description): return type(value).__new__(Internal) – grasshopper Jan 05 '15 at 17:06
  • I was trying to figure out how to do this with a metaclass but couldn't quite get there. If possible using a metaclass, would an additional example be worthwhile in this answer? – wwii Jan 05 '15 at 17:10
  • 1
    @wwii, I'm not sure how I'd do it with a metaclass (though I guess a MC *could* be abused this way) and I see no advantage in trying one (MC's key plus is when you need weird behavior to propagate by subclassing, but we're not looking for that here) -- factory functions are under-utilized wrt their usefulness, and MCs have a "cool" factor that may lead to them being used too often... I'd rather not encourage that, and promote simplicity instead:-) – Alex Martelli Jan 05 '15 at 17:27
0

The built-in repr() of an object is a string. You can modify it with the repr library, but I'm not sure it can be a different type. There are some additional details/ ideas in this SO question.

Community
  • 1
  • 1
Tom
  • 22,301
  • 5
  • 63
  • 96
0

If I understand you correctly, you want to have types/classes that behave normally like their type, and additionally have a 'description' field. How you go about it is strange - I believe the statement

type(value).__init__(value)

has no effect whatsoever, though I am perplexed why python doesn't throw a warning for this. Surely .init calls within an init should be reserved for super classes.

I would do the following and use s.value instead of s:

class Setting(object):
  def __init__(self, value, description=None):
    self.value = value
    self.description = description

s = Setting(5)

print s.value + 5
print s.description
grasshopper
  • 3,988
  • 3
  • 23
  • 29
  • 1
    The problem with this is that i have to do `s.value` every time i want to use the object, which is exactly what i'm trying to avoid. – will Jan 05 '15 at 16:33