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.

5
  • 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. Commented Jan 15, 2013 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) Commented Jan 15, 2013 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! Commented Jan 15, 2013 at 14:25
  • @MartijnPieters -- Hmmm... Alright then. Time to go re-read about descriptors :) Commented Jan 15, 2013 at 14:25
  • @mgilson: added that as an answer, it works just fine. :-) Commented Jan 15, 2013 at 14:29

4 Answers 4

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().

Sign up to request clarification or add additional context in comments.

Comments

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'

1 Comment

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...
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.

Comments

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

4 Comments

You're right -- just copied code from testing another ideas. Fixed. Thank you.
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 :)
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.
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 ...

Your Answer

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.