6

I am attempting to add a variable to a class that holds instances to the class. The following is a shortened version of my code.

class Classy :
    def __init__(self) :
        self.hi = "HI!"
    # "CLASSIES" variable holds instances of class "Classy"
    CLASSIES = []
    for i in xrange(0,4) :
        CLASSIES.append(Classy())

Upon running the code, I get the following error.

Traceback (most recent call last):
  File "classy.py", line 6, in Classy
    CLASSIES.append(Classy())
NameError: name 'Classy' is not defined

Is there another way to add instances of a class to a class/static variable within that class?

Tanaki
  • 2,575
  • 6
  • 30
  • 41
  • The real question is what are you trying to accomplish with this move – joojaa Feb 12 '13 at 20:33
  • 1
    @joojaa I wouldn't say the use cases for this are *that* obscure. – Gareth Latty Feb 12 '13 at 20:34
  • Well it is a bit, I would understand if you actually initialized the children but putting them in class variable makes this obscure. You basically initiating something that will be one that has 4 different instances of fixed other selfs. But there could be any number of different goto guys. The weird thing is that the classy instance is not part of the list, i would understand this if the initiated class would be part of the list. Or are you looking at a array of Borgs? – joojaa Feb 12 '13 at 20:37

4 Answers4

2

The simplest way to do this is do it after the class is created, when the class has been defined, and therefore can be used:

class Classy :
    CLASSIES = []

    def __init__(self) :
        self.hi = "HI!"

Classy.CLASSIES = [Classy() for _ in xrange(0,4)]

(Here using a list comprehension for convinience, as it's the most readable and efficent way to build a list).

Also note that if this intended to be a constant, you should probably make it a tuple rather than a list, and if it isn't intended to be, you should probably not use an ALL_CAPS name which, by convention, implies a constant.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • Yes, the **list comp.** builds a list and that **creates a new list**, different from the one that resulted of the execution of the definition of the class, while it may be important that the same list would be only **updtaed** . See my edit in my answer. – eyquem Feb 12 '13 at 21:45
  • @eyquem This is the initial creation, so it doesn't matter. The emulate that, simply remove the initial creation in the class - I just felt it was worth putting there as it is highly unlikely it will matter, and it gives a hint that the list will exist, (including to editors). If you, for some reason, *need* it to exist and the identity to remain unchanged, simply do `+=` instead of `=`. – Gareth Latty Feb 12 '13 at 22:06
  • Well, if it doesn't matter because it's the initial creation , then the line ``CLASSIES = []`` is not necessary in the class' definition. That's the case in BrenBarn's code – eyquem Feb 12 '13 at 22:20
  • @eyquem Yes, it is not necessary, however, it is a good idea. It lets the reader of the code know that class attribute exists, and it lets any editor with auto-complete functionality know it exists easily. This makes it worthwhile, even if it's isn't needed functionally. It makes the code more readable. – Gareth Latty Feb 12 '13 at 23:02
  • By the way, have you been convinced by the pretentio of delnan saying that CLASSIES is of global level ? Did I musunderstood sommething ? It appears to me to be senseless and unsustainable. Where does he pull this strange idea from ? – eyquem Feb 12 '13 at 23:19
2

The class body is executed before the class is created. Therefore, you are attempting the instantiate the class before it exists. You can still attach instances to the class, but you have to create them after the class body finished, e.g.:

class Classy(object):
    def __init__(self):
        self.hi = "HI!"
    CLASSIES = []

for i in xrange(4):
    Classy.CLASSIES.append(Classy())

However, I'd suggest you first think long and hard whether you actually need this effectively-global list, and whether you need it to be part of the class object. Personally, I almost never do something like this.

eyquem
  • 26,771
  • 7
  • 38
  • 46
  • This is a dupe of my answer. Actually, you also made the same mistake I did - it needs to be `Classy.CLASSIES.append()` too. – Gareth Latty Feb 12 '13 at 20:32
  • @Lattyware Is it really a dupe if it contains significantly more explanation beside the code? Besides, the code differs a bit too. But thanks for pointing out the mistake! –  Feb 12 '13 at 20:35
  • Your explanation is a duplicate of what BrenBarn says in his answer, and your suggestion at the end seems misguided to me - it's not *effectively global* - it's class level, and while it's rare, there are use cases for something like this that are not so far fetched. The code difference is also trivial. It's not a bad answer, just I don't really see it adds anything that hasn't been said. – Gareth Latty Feb 12 '13 at 20:36
  • @Lattyware While it's unfortunate that we now have three answers with largely overlapping contents, I don't think one of them coming in a bit later means we have to remove any. RE technical matters: The list *is* effectively global -- the class is global, and the class attribute is once-per-class, hence also (1) accessible from everywhere and (2) shared by everyone accessing it. If it's not too much trouble, I'd like to hear a use case -- I wrote truth, I almost never see code like this. –  Feb 12 '13 at 20:40
  • Yes the use case of this code would be nice to know. Since it seems like a very good way of shooting one self in the foot. BTW. its still a dupe even if you type slower tough it has no bearing on my discussion. – joojaa Feb 12 '13 at 20:45
  • Say, for example, you have a class representing People, some of those people are mentors, and each person is automatically assigned a mentors when they are added to a system. The list stores the mentors (which are randomly picked from when a new person is created). This would be a legitimate way to implement that system. – Gareth Latty Feb 12 '13 at 20:45
  • As to being global because it's parent is global, that just implies you shouldn't have class attributes at all, as most Python classes are defined at a module level. Clearly that makes little sense. And if a third answer is just a combination of the information in two others, I still don't see the benefit to SO of having it there. – Gareth Latty Feb 12 '13 at 20:47
  • Yes but brittle. Its much better to put the mentors in a list that is part of the instance of the class. And just then init in a shared list to those that need to share it. This way if you need to extend the code to handle say another department it would work easily. – joojaa Feb 12 '13 at 20:48
  • @Lattyware Okay, I see. The use of an (effectively) global data structure disqualifies such a design in many contexts, which is probably why such a thing never crosses my mind. And of course in other domains, such as a database-driven web app, there are better solutions (e.g. utilizing the ORM). –  Feb 12 '13 at 20:50
  • I'm definitely not saying it's a common thing to need, but it's something that could make sense, in certain contexts. – Gareth Latty Feb 12 '13 at 20:51
  • @Lattyware RE global: Yes, you should treat class members exactly as other globals, because they share exactly the same problems. I'm not saying we shouldn't have any globals (including class members) -- there clearly are many valid use cases (constants; functions/methods; caches; some data is inherently global; and I'm sure there are many more examples). However, we must be aware that they're the same thing in a different disguise. RE not common: That's why I wrote "think hard whether you need it", not "you shouldn't do this" ;-) –  Feb 12 '13 at 20:52
  • @Lattyware Yes it might have some use cases. Tough I can see so many better ways to accomplish the same thing. – joojaa Feb 12 '13 at 20:55
  • Understood; I guess there's really no difference from what I was trying to do! FYI I avoided mentioning my actual use-case to avoid this question devolving into "NO DO IT THIS WAY THAT'S WRONG", but the class is actually a "Machine" class, and each machine represents one machine on a LAN; I get the list of machines by parsing dhcpd.conf, and that's what fills the static class variable. – Tanaki Feb 12 '13 at 21:06
  • @Tanaki That actually seems like a pretty decent use case for quasi-global data. Not perfect (I can see tests becoming more complicated, *if* you write any) but decent. –  Feb 12 '13 at 21:11
  • @Tanaki Are there always only 4 machines to register in CLASSIES by parsing dhcpd.conf ? – eyquem Feb 12 '13 at 21:28
  • @delnan I really don't see how you can dare to say that CLASSIES is global. You wrote < _the class is global, and the class attribute is once-per-class, hence also (1) accessible from everywhere_ > What does it mean "once-per-class" ? And it's not accesible from everywhere since it need to be attained by ``Classy.CLASSIES`` when we are in the global scope. - < _you should treat class members exactly as other globals, because they share exactly the same problems._ > That's an affirmation that will be hardly proven. Did you make a census of all the potential problems and tested them all ? – eyquem Feb 12 '13 at 22:34
  • @delnan By the way, please see the first edit in my answer concerning the globality. – eyquem Feb 12 '13 at 22:36
  • @eyquem Its a class attribute, that is the list is the same across all instances, its shared. Since the class is global so is the list. However its not usually consider global, even if it is. Point is this kind of approach has drawbacks that are sort of identical to globals. Using class attributes is valid however this use is weird as its going to make a very odd class that has very unintuitive otherworldly properties. – joojaa Feb 13 '13 at 06:14
2

It seems to me that you want to obtain that:

class Classy :
    CLASSIES = []
    def __init__(self) :
        self.hi = "HI!"
        Classy.CLASSIES.append(self)

for i in xrange(4):
    Classy()

for x in  Classy.CLASSIES:
    print x

result

<__main__.Classy instance at 0x011DF3F0>
<__main__.Classy instance at 0x011DF440>
<__main__.Classy instance at 0x011DF418>
<__main__.Classy instance at 0x011DF2B0>

EDIT

Note that with the code of Lattyware:

class Classy :
    CLASSIES = []
    idC = id(CLASSIES)
    def __init__(self) :
        self.hi = "HI!"
        #Classy.CLASSIES.append(self)


Classy.CLASSIES = [Classy() for _ in xrange(0,4)]

print Classy.idC
print id(Classy.CLASSIES)
print 'Classy.idC==id(Classy.CLASSIES) :',Classy.idC==id(Classy.CLASSIES)

result

18713576
10755928
Classy.idC==id(Classy.CLASSIES) : False

While with the for loop of delnan'code, it doesn't appear.

However it's easy to correct:
writing
Classy.CLASSIES[:] = [Classy() for _ in xrange(0,4)]
or
Classy.CLASSIES.extend(Classy() for _ in xrange(0,4))
instead of
Classy.CLASSIES = [Classy() for _ in xrange(0,4)]
it depends of what is desired.

EDIT 2

Methods may reference global names in the same way as ordinary functions. The global scope associated with a method is the module containing its definition. (A class is never used as a global scope.)

http://docs.python.org/2/tutorial/classes.html#class-definition-syntax

A class has a namespace implemented by a dictionary object. Class attribute references are translated to lookups in this dictionary, e.g., C.x is translated to C.__dict__["x"]

http://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes

class Classy :
    CLASSIES = []

print '"CLASSIES" in globals()',"CLASSIES" in globals()
print '"CLASSIES" in Classy.__dict__ ==',"CLASSIES" in Classy.__dict__

result

"CLASSIES" in globals() False
"CLASSIES" in Classy.__dict__ == True

Delnan, how will you continue to pretend that CLASSIES is global ??
Did I misunderstood something in your debate with Lattyware ?

eyquem
  • 26,771
  • 7
  • 38
  • 46
  • This presumes that the OP wants *every* class to be added to the list. – Gareth Latty Feb 12 '13 at 21:09
  • @Lattyware Yes, it presumes, but I wrote "it seems to me"", meaning I wasn't sure. And I'm still unsure. An annoying thing is that it's often hard to understand the questions because the questionners don't give enough info on what they really want and the context of the problem. – eyquem Feb 12 '13 at 21:23
  • This isn't really a flaw - why would you make another reference to that list, and not access it through `Classy.CLASSIES` - it's possible, but you would have to do it before finishing the definition of the class, which means you would never need to do it. – Gareth Latty Feb 12 '13 at 22:09
  • @Lattyware I don't understand your last comment. What do you speak of ? – eyquem Feb 12 '13 at 22:18
  • The fact the list comprehension is a new list is irrelevant - it's never going to matter as there is no use case for creating a reference to the old list. – Gareth Latty Feb 12 '13 at 23:01
  • @Lattyware Never knows. It depends of the aim of the user of the code. Which we don't know, so we must stop to make hypothesises – eyquem Feb 12 '13 at 23:16
  • We do know, because the only way that the user would create an instance of the old list is to make a new reference inside the class (as the new list is made directly after the creation of the class). This would never be done as anywhere inside the class, you can access it as `Classy.CLASSIES`, hence have no need to make a new reference. – Gareth Latty Feb 12 '13 at 23:24
  • @Lattyware Who knows what could happen in case this class would be used as a base classe in an inheritance – eyquem Feb 13 '13 at 00:13
  • When I say it's effectively global, I don't mean it's in `globals()`. I mean it's global in the same sense things in `globals()` are global: They are accessible from everywhere and there are no separate things for different scopes. These properties are what cause the problems associated with global variables, not that they happen to occur in the dictionary that happens to be returned by a function that happens to be called `globals`. –  Feb 13 '13 at 13:09
1

The class itself is not defined until after the class block finishes executing, so you can't make use of the class inside its own definition.

You could use a class decorator or a metaclass to add your desired class variable after the class is created. Here's an example with a decorator.

def addClassy(cls):
    cls.CLASSIES = [cls() for a in xrange(4)]
    return cls

@addClassy
class Classy(object):
    pass

>>> Classy.CLASSIES
0: [<__main__.Classy object at 0x000000000289A240>,
 <__main__.Classy object at 0x000000000289A518>,
 <__main__.Classy object at 0x000000000289A198>,
 <__main__.Classy object at 0x000000000289A208>]
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • While this works, it feels a little... off - to me it makes it far harder to see that the class has the CLASSIES attribute, and what will be in it. – Gareth Latty Feb 12 '13 at 20:38
  • 1
    @Lattyware: It depends how often you need to do this. If you need this pattern for multiple classes, it makes sense to create one decorator to do it instead of repeating code to do it manually for each class. – BrenBarn Feb 12 '13 at 20:39
  • In that case, it would make more sense, yes. – Gareth Latty Feb 12 '13 at 20:41
  • @BrenBarn I didn't verify by executing a code, but it seems to me that **addClassy( )** ADDS the attribute **CLASSIES** to the class, even when there is already one in it (which is not the case of your code). It may be important in some case that the list doesn't change. See the edit in my answer, your code is similar to the one of Lattyware on this point. – eyquem Feb 12 '13 at 21:53