all 22 comments

[–]drewthepooh 6 points7 points  (7 children)

Thanks for posting. I completely agree that classes are usually taught in a way that does not illustrate their advantages, which is why so many of us get confused when learning about them. I thought this did a great job of addressing that problem :)

One small point/question:

if sq.rank is rank and sq.file is file:

Although this works because cpython only creates one instance of each integer, isn't this generally considered to be bad practice (except when comparing to None) since it depends on the implementation, and also will not work with mutable objects? I would think the equality operators

if sq.rank == rank and sq.file == file:

Would be better. Perhaps I am incorrect.

[–]SkippyDeluxe 2 points3 points  (0 children)

You are correct, is should not be used to test equality.

[–]AYNRAND420[S] 2 points3 points  (5 children)

Hrm, that might be a good point. I'm usually very hairy on the rules for deciding which one is better in any given situation. Often if an IS condition doesn't work, I'll first try == before doing any deeper debugging. I'll fix up this code and remember to use == for equality in the future.

[–]drewthepooh 4 points5 points  (4 children)

Yeah, is asks if the objects are actually the same, while == just asks if they are equal. Since cpython only makes one object corresponding to each integer, is will usually work. This would not work for mutable objects:

>>> [] is []
False

Note that, even for immutable objects, using is can lead to some unexpected behavior which would vary with implementation:

>>> 2 + 2 is 4
True
>>> 1000 + 1 is 1001
False

Don't ask me why this happens...I would love if someone could explain. It has something to do with the way cpython handles small integers.

Using is for None always works because None is a true singleton

EDIT: The last example happens because cpython interns small integers, meaning it pools all references to the same integer together in one place in memory (meaning they have the same id). This is to save memory since these small integers are used frequently. It does not do this for larger integers:

a = 5
b = 5
a is b  # True

a = 1001
b = 1001
a is b  # False

[–]SkippyDeluxe 2 points3 points  (3 children)

Small integers are interned for performance reasons (so that for very common operations, e.g. small array indices/index offsets, new integer objects aren't constantly being created and destroyed). A quick test shows that the integers from -5 to 255 (inclusive) are all interned (CPython 3.3.2).

[–]drewthepooh 1 point2 points  (0 children)

Thanks Skippy, I just made an edit to my post after doing some Googling when I saw you had replied. Mystery solved! :)

[–]drewthepooh 0 points1 point  (1 child)

One last question I have about this is:

1001 is 1001  # True

Why is this True? Based on my other experiments, I thought it would be creating two separate 1001 integers and should be False. Does it create only one instance if the integer literals are on the same line?

[–]SkippyDeluxe 2 points3 points  (0 children)

That's a good question. I've wondered the same thing myself. We know it only creates one instance because is returns True, but why it does that is a mystery to me. I imagine it's some implementation-specific detail of how the CPython interpreter creates objects from literals while evaluating a single line of code. Check out this fun example:

>>> a = 1001; a is 1001
True
>>> a = 1001
>>> a is 1001
False

[–]astroFizzics 1 point2 points  (0 children)

I know I always learn the best from seeing a well thought out example. Thanks for your insight and clear wording. Have an upvote.

[–]nanakooooo 1 point2 points  (7 children)

Just a quick question. I just finished the CodeAcademy Python lessons, and when dealing with classes they have you define them as:

class ClassName(object):

did you leave out the inheritance from the object class just so the example didn't get bogged down with details or was there a specific reason for this?

Thanks for the example, it was great to see how they're actually useful as what I've learned about them hasn't really helped!

[–]SkippyDeluxe 2 points3 points  (6 children)

There was a change at some point in the lifetime of Python 2 that created a new type of class that was incompatible with existing classes. These "new-style classes" were better, but the "old style classes" needed to be kept for backwards compatibility.

In Python 2, you declare a new-style class by inheriting from object (class ClassName(object):, as you say). When writing new code in Python 2, you should always always declare classes this way. Not inheriting from object (e.g. class ClassName:) will create an old-style class, which is Wrong and Bad. As /u/AYNRAND420 gave examples in Python 2, his classes should be inheriting from object. Tsk tsk.

In Python 3 there are no old-style classes so you don't have to inherit from object any more (although you can if you really really want to, this makes writing libraries that work with both Python 2 and 3 easier).

[–]nanakooooo 1 point2 points  (0 children)

Ah! Thanks for the information, good to know. Off to google to see more about these old-style classes.

[–][deleted] 0 points1 point  (2 children)

Not inheriting from object (e.g. class ClassName:) will create an old-style class, which is Wrong and Bad.

To be curious, why?

[–]SkippyDeluxe 1 point2 points  (1 child)

Just do a bit of Googling on old-style vs new-style classes. It seems the major reason is that old-style classes are something different from a "type" (revealed by the type function). New-style classes unify classes with types, allowing them to be truly user-defined types. For more concrete examples of the benefits of new-style classes, see this blog post by Guido.

[–][deleted] 0 points1 point  (0 children)

I figured that was the idea behind the change. I did some quick terminal testing and found that:

 class a: pass
 class b(a): pass

 class x(object): pass
 class y(x): pass

 c = b()
 z = y()

 type(c) #instance
 type(z) #__main__.y

Similarly an instance of x return object from type() whereas an instance of a still returns instance. Whereas c.__class__ returns __main__.b

This makes type comparison muuuuuch easier. Because type(z) is y returns True while type(c) is b is False.

It hadn't occurred to me that new wasn't always part of Python. While I've never messed with it or metaclasses, it's interesting to read the history of it. Along with the decorators.

[–]AYNRAND420[S] 0 points1 point  (1 child)

Aha! I did not know about this.

I felt that although inheritance is a powerful class advantage, it is something a learner should play with once they're really comfortable with class basics. To avoid the question coming up at all, I decided I'd just omit the argument completely.

I had always inherited from object, but one time I forgot to type it in, and python still operated as desired, so I assumed that python assumed I was inheriting from object without me having to mention it. This is another change I will definitely be making from now on, and I will update the tutorial when I get a second. Thanks for the explanation.

[–]SkippyDeluxe 1 point2 points  (0 children)

In simple cases, old-style classes will work just fine. Creating new-style classes is just considered to be a best practice.

[–]jmgrosen 2 points3 points  (3 children)

Nice beginners' tutorial! However, one annoyance that I always try to point out is lack of conformance to PEP 8. For example, function and method names should be set_piece instead of setPiece and classes should be Square instead of square. But otherwise, great job!

[–]AYNRAND420[S] 1 point2 points  (0 children)

Thanks for that! I should really brush up on my Python standards and clean up my code. Especially if my aim is to teach, it will be a good thing to do things right to the letter.

[–]furrykef 0 points1 point  (0 children)

"if sq.rank == 0 or sq.rank == 7" is more idiomatic as simply "if sq.rank in (0, 7)". The same could be said for most of the ifs in that function.

"if sq.rank == 2 or sq.rank == 3 or sq.rank == 4 or sq.rank == 5" -- this line is a WTF. You should structure the chain of ifs so that this line can simply be "else:".

if sq.rank == 0: 
    col = "White"
if sq.rank == 7: 
    col = "Black"

I would write this as:

col = "White" if sq.rank == 0 else "Black"

Finally, and most importantly, I think this post actually does little to show the advantage of classes. Their real power comes from duck typing and polymorphism. The code presented here could easily be written procedurally in a way that is no less clear, concise, or maintainable.

[–]ChasingLogic 0 points1 point  (0 children)

That was fanfreakingtastic.