2

I have a Category class which has different names for each categories, the names of the categories can be unknown, good and bad, all categories share the same behavior so i don't want to create sub classes for each type of category, the problem comes when i am trying to create the different categories in this way:

Category.GOOD

This statement should return a category object with his name setting to 'good' so i try the following:

class Category(object):                                                                                                            

  def __init__(self, name):                                                                                                      
      self.name = name                                                                                                           

  @property
  def GOOD(self):
      category = Category(name='good')                                                                                           
      return category                                                                                                            

  @property
  def BAD(self): 
      category = Category(name='bad')                                                                                            
      return category   

Then i created and use the category with the following output:

c = Category.GOOD
c.name
AttributeError: 'property' object has no attribute 'name'

Realizing that this doesn't work i try a java like approach:

class Category(object):                                                                                                          

    GOOD = Category(name='good')
    BAD = Category(name='bad')                                                                                                   

    def __init__(self, name):
        self.name = name

What i get here is a undefined name "Category" error, so my question is if there is a pythonic way to create a category object like this.

angvillar
  • 1,074
  • 3
  • 10
  • 25
  • You cannot do this out-of-the-box with a `property`; you'd have to create your own descriptor instead, or just use a `classmethod` and call `GOOD()`, as given below. – Martijn Pieters Jan 15 '13 at 14:19
  • @MartijnPieters -- It's not possible to create a descriptor with this behavior is it? The problem is that he's accessing the descriptor on the class (which is just an attribute and none of the special descriptor magic comes into play) – mgilson Jan 15 '13 at 14:23
  • @mgilson: descriptors are invoked for classes as well; `classmethod` is a descriptor too. `function.__get__(None, cls)` gives a 'unbound method' in Python 2 (returns `self` in Python 3), so it is definitely invoked! – Martijn Pieters Jan 15 '13 at 14:25
  • @MartijnPieters -- Hmmm... Alright then. Time to go re-read about descriptors :) – mgilson Jan 15 '13 at 14:25
  • @mgilson: added that as an answer, it works just fine. :-) – Martijn Pieters Jan 15 '13 at 14:29

4 Answers4

2

You probably want to use classmethods:

class Category(object):

    @classmethod
    def GOOD(cls):
        category = cls(name='GOOD')
        return category

Now you can do c = Category.GOOD().

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
2

You cannot do this with a property; you either have to use a classmethod, or create your own descriptor for that:

class classproperty(property):
    def __get__(self, inst, cls):
        return self.fget(cls)

I'm abusing the property decorator here; it implements __set__ and __del__ as well, but we can just ignore those here for convenience sake.

Then use that instead of property:

class Category(object):
    def __init__(self, name):
        self.name = name

    @classproperty
    def GOOD(cls):
        return cls(name='good')

    @classproperty
    def BAD(cls):
        return cls(name='bad')

Now accessing Category.GOOD works:

>>> Category.GOOD
<__main__.Category object at 0x10f49df50>
>>> Category.GOOD.name
'good'
jeromej
  • 10,508
  • 2
  • 43
  • 62
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Upvoted. In hind-sight, it seems clear that descriptors would need to work on classes (and not just instances) in order to have `classmethod` or `staticmethod` work. And to think that I've had the wrong idea about these things since I taught myself descriptors a couple months ago... – mgilson Jan 15 '13 at 14:32
1

I'd use module variables for this. Consider you have the module category.py:

class Category(object):
    # stuff...

now you put the two global objects in it:

GOOD = Category(name='good')
BAD = Category(name='bad')

You can use it like that:

from path.to.category import GOOD, BAD

I don't say that this is pythonic but I think this approach is elegant.

Constantinius
  • 34,183
  • 8
  • 77
  • 85
0

The main point that you could not use class definition inside that class definition itself. So the most straight way to achieve what you are want is to use class/static methods as shown below, or even package constants.

class Category(object):
    def __init__(self, name):
        self.name = name

    @classmethod
    def GOOD(cls):
        return Category(name='good')

    @classmethod
    def BAD(cls):
        return Category(name='bad')

print Category.GOOD().name

or

class Category(object):
    def __init__(self, name):
        self.name = name

    @staticmethod
    def GOOD():
        return Category(name='good')

    @staticmethod
    def BAD():
        return Category(name='bad')

print Category.GOOD().name
Vladimir
  • 9,913
  • 4
  • 26
  • 37
  • You're right -- just copied code from testing another ideas. Fixed. Thank you. – Vladimir Jan 15 '13 at 14:27
  • As a side note, I didn't like writing `GOOD` in all caps. It seemed too much like yelling ... In any event, this looks correct now :) – mgilson Jan 15 '13 at 14:29
  • Yes, you're completely right that all-caps function name violates python naming convention. But the question was about feature implementation and not about code style. Hence, the less code changes introduced the more understandable answer is. – Vladimir Jan 15 '13 at 14:33
  • Yeah, that's fine (not changing it is the right thing to do). I just wanted you to know that I wasn't yelling at you ... I try to be a friendly fellow around here for the most part ... – mgilson Jan 15 '13 at 14:34