views:

214

answers:

5

Hello Pythoneers: the following code is only a mock up of what I'm trying to do, but it should illustrate my question.

I would like to know if this is dirty trick I picked up from Java programming, or a valid and Pythonic way of doing things: basically I'm creating a load of instances, but I need to track 'static' data of all the instances as they are created.

class Myclass:
        counter=0
        last_value=None
        def __init__(self,name):
                self.name=name
                Myclass.counter+=1
                Myclass.last_value=name

And some output of using this simple class , showing that everything is working as I expected:

>>> x=Myclass("hello")
>>> print x.name
hello
>>> print Myclass.last_value
hello
>>> y=Myclass("goodbye")
>>> print y.name
goodbye
>>> print x.name
hello
>>> print Myclass.last_value
goodbye

So is this a generally acceptable way of doing this kind of thing, or an anti-pattern ?

[For instance, I'm not too happy that I can apparently set the counter from both within the class(good) and outside of it(bad); also not keen on having to use full namespace 'Myclass' from within the class code itself - just looks bulky; and lastly I'm initially setting values to 'None' - probably I'm aping static-typed languages by doing this?]

I'm using Python 2.6.2 and the program is single-threaded.

+1  A: 

Is this pythonic? Well, it's definitely more pythonic than having global variables for a counter and the value of the most recent instance.

It's said in Python that there's only one right way to do anything. I can't think of a better way to implement this, so keep going. Despite the fact that many will criticize you for "non-pythonic" solutions to problems (like the needless object-orientation that Java coders like or the "do-it-yourself" attitude that many from C and C++ bring), in most cases your Java habits will not send you to Python hell.

And beyond that, who cares if it's "pythonic"? It works, and it's not a performance issue, is it?

Rafe Kettler
I agree- It does work - and it reads ok to me (apart from those 'sticking-out' class-fields - can I make them private?) - but if there happened to be a more common pattern out there, and it made sense to me, I would adopt it, to keep to a more 'standard' style going forward. Thanks for the answer.
monojohnny
In my experience, Python isn't big on "object-oriented design patterns", though there are certain idioms that I'm still learning (`while True-break` is the best example). Also, there isn't really that much data hiding built into Python, the most common method of designating something as private is preceding it with a double underscore (e.g. `__counter`), but that only really helps when you write APIs.
Rafe Kettler
@Rafe Kettler: Actually, the double underscore is to use python's name mangling. To mark it as private, you'd use a single underscore by convention
Daenyth
@Rafe Kettler: double underscoring does some name mangling but does not enforce privateness in any way (although the intent should be clear). It's more common to use a single underscore to *indicate* ~privateness~.
ChristopheD
Thanks, this is the first "straight-talk" I've been able to get on Python and privacy.
Rafe Kettler
@Rafe Kettler: "Python isn't big on "object-oriented design patterns", though"? What? It's packed full of them. They are different from Java and C++, but they're certainly is a mountain of OO design patterning in Python.
S.Lott
Useful discussion - thanks for your input on this. Cheers
monojohnny
+7  A: 

Class variables are perfectly Pythonic in my opinion.

Just watch out for one thing. An instance variable can hide a class variable:

x.counter = 5  # creates an instance variable in the object x.
print x.counter  # instance variable, prints 5
print y.counter  # class variable, prints 2
print myclass.counter # class variable, prints 2
Mark Lutton
Thanks for this - not quite sure I follow what's going on there ? what's the link between y.counter and myclass.counter ?
monojohnny
+1, this is important to note. @monojohnny: while the class in general has a state variable `counter`, that variable can be changed for an individual instance.
Rafe Kettler
That little "gotcha" (whose counter is this?) could be a good reason for not using class variables. Note that there is a close counterpart in JavaScript prototype inheritance. When you create an object in JavaScript with the keyword "new", you can override a method by assigning it as a property. If the method exists in the object, it is used; otherwise the method in the prototype is used. If you are used to this sort of thing, Python's class variables sort of make sense.
Mark Lutton
Thanks Mark - that rings a bell with javascript actually...that you can simply add stuff to 'objects' after they are created: which (coming from a less dynamic-language background at least) has suprised me from time to time. Cheers
monojohnny
OK , I'm going vote this my accepted answer as it contained a useful warning about possible dangers of class variables: so I learnt something. Cheers
monojohnny
Sadly, however, the issue of conflating too many unrelated responsibilities in a single class is not addressed by this design. static class-level variables are -- universally -- a sign of unrelated responsibilities in a single class.
S.Lott
I deleted my comment on the question, but I'll repeat it here-- just make sure to properly handle simultaneous reads/updates to the class properties if your app is multi-threaded. (If it's single-threaded, then this isn't an issue.)
ma3
+3  A: 

Do. Not. Have. Stateful. Class. Variables.

It's a nightmare to debug, since the class object now has special features.

Stateful classes conflate two (2) unrelated responsibilities: state of object creation and the created objects. Do not conflate responsibilities because it "seems" like they belong together. In this example, the counting of created objects is the responsibility of a Factory. The objects which are created have completely unrelated responsibilities (which can't easily be deduced from the question).

Also, please use Upper Case Class Names.

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

def myClassFactory( iterable ):
   for i, name in enumerate( iterable ):
       yield MyClass( name )

The sequence counter is now part of the factory, where the state and counts should be maintained. In a separate factory.

[For folks playing Code Golf, this is shorter. But that's not the point. The point is that the class is no longer stateful.]

It's not clear from question how Myclass instances get created. Lacking any clue, there isn't much more than can be said about how to use the factory. An iterable is the usual culprit. Perhaps something that iterates through a list or a file or some other iterable data structure.

Also -- for folks just of the boat from Java -- the factory object is just a function. Nothing more is needed.


Since the example on the question is perfectly unclear, it's hard to know why (1) two unique objects are created with (2) a counter. The two unique objects are already two unique objects and a counter isn't needed.

For example, the static variables in the Myclass are never referenced anywhere. That makes it very, very hard to understand the example.

x, y = myClassFactory( [ "hello", "goodbye" ] ) 

If the count or last value where actually used for something, then a perhaps meaningful example could be created.

S.Lott
+1 much better than my deleted answer
aaronasterling
Not sure I actually follow your directive that stateful classes should be avoided (or why they are intrinsically hard to debug) - but appreciate the code-snippet: could you edit and add in a simple usage of the 'myClassFactory' ? (not sure what 'some_source' should be...) Thanks
monojohnny
From PEP8: "Avoid extraneous whitespace in the following situations: - Immediately inside parentheses, brackets or braces.".
tokland
1) Stateful classes are unexpected and atypical. The fact that you bothered to ask indicates their status as an outlier. 2) Stateful classes conflate two unrelated responsibilities: state of object creation and the created objects. Do not conflate responsibility because it "seems" like they belong together. Your counting of created objects is the responsibility of a Factory. The objects which are created have unrelated responsibilities.
S.Lott
@tokland: I was about to say the same but then reasoned the extraenous spaces might have been added for emphasis. While nitpicking, I would also write `self, name` instead of `self,name` (but that's probably just a small typo).
ChristopheD
@toklad: After 30 years of extraneous whitespace inside ()'s, I'm unable to change. You, however, may have enough reputation to fix it and make it pleasing to your eye.
S.Lott
With respect though: 1. 'unexpected' and 'atypical' are entirely relative terms: that statement doesn't constitute a reason why stateful classes should or should not be used. The fact that I asked a question doesn't confirm anything one way or the other (apart maybe that I didn't know, so had to ask...) 2. Makes sense to me.
monojohnny
@S.Lott: so to contrast the use of the factory object (just to complete your code snippet), instead of using "x=Myclass("hello")", what would be the one line to provide an example of the factory here ? (I want to see if this would fit with what I'm doing or not).
monojohnny
//It's not clear from question how Myclass instances get created. Lacking any clue// ??? x=myclass("hello") was right in there from the start?
monojohnny
@monojohnny: Creating two constant value objects is silly. Why count to 2 when the example obviously has only two objects? There's no point to it. Why have static counter variables that are never used? It's equally silly. Given such a degenerate case, it's impossible to provide a meaningful example of use.
S.Lott
@monojohnny: "'unexpected' and 'atypical' are entirely relative terms" Not really. I read a lot of code. It's unexpected and atypical.
S.Lott
@monojohnny: another example of the usage of the above: http://paste.pocoo.org/show/269269/
ChristopheD
erm...well I did say a 'mock-up' right from the start. Thanks for the edit, showing the use of the factory.
monojohnny
@monojohnny: "well I did say a 'mock-up' right from the start". So? It's still woefully incomplete and difficult to create a counter-example for.
S.Lott
A really useful Java thing is static final class members, i.e. constants. That would be a great use of class variables in Python, except that there's no simple keyword like "final" to make a Python class variable immutable. So if mutable class variables are bad and immutable ones are impossible ....?
Mark Lutton
@Mark Lutton: You're not talking about stateful class variables. `final` class variables are unrelated to stateful class variables.
S.Lott
+1  A: 

You don't have to use a class variable here; this is a perfectly valid case for using globals:

_counter = 0
_last_value = None
class Myclass(obj):
    def __init__(self, name):
        self.name = name

        global _counter, _last_value
        _counter += 1
        _last_value = name

I have a feeling some people will knee-jerk against globals out of habit, so a quick review may be in order of what's wrong--and not wrong--with globals.

Globals traditionally are variables which are visible and changeable, unscoped, from anywhere in the program. This is a problem with globals in languages like C. It's completely irrelevant to Python; these "globals" are scoped to the module. The class name "Myclass" is equally global; both names are scoped identically, in the module they're contained in. Most variables--in Python equally to C++--are logically part of instances of objects or locally scoped, but this is cleared shared state across all users of the class.

I don't have any strong inclination against using class variables for this (and using a factory is completely unnecessary), but globals are how I'd generally do it.

Glenn Maynard
Thanks Glenn; useful alternative way of doing it. (and more readable code IMHO than my initial approach).
monojohnny
+2  A: 

You can solve this problem by splitting the code into two separate classes.

The first class will be for the object you are trying to create:

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

And the second class will create the objects and keep track of them:

class MyClassFactory(object):
    Counter = 0
    LastValue = None

    @classmethod
    def Build(cls, name):
        inst = MyClass(name)
        cls.Counter += 1
        cls.LastValue = inst.Name
        return inst   

This way, you can create new instances of the class as needed, but the information about the created classes will still be correct.

>>> x = MyClassFactory.Build("Hello")
>>> MyClassFactory.Counter
1
>>> MyClassFactory.LastValue
'Hello'
>>> y = MyClassFactory.Build("Goodbye")
>>> MyClassFactory.Counter
2
>>> MyClassFactory.LastValue
'Goodbye'
>>> x.Name
'Hello'
>>> y.Name
'Goodbye'

Finally, this approach avoids the problem of instance variables hiding class variables, because MyClass instances have no knowledge of the factory that created them.

>>> x.Counter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'Counter'
RevolXadda
+1 - I Read this too late to vote as accepted answer - but thanks for this - the code and the benefits of doing it this way. Cheers.
monojohnny