Does Python really follow its philosophy of "Readability counts"?

M

Madhusudan.C.S

I am sorry all I am not here to just blame Python. This is just an
introspection of whether
what I believe is right. Being a devotee of Python from past 2 years I
have been writing only
small apps and singing praises about Python where ever I go. I now got
a chance to read
Django's code for some reason. I have now strongly started feeling if
Python really follows its
"Readability Counts" philosophy. For example,

class A:
a = 10
b = "Madhu"

def somemethod(self, arg1):
self.c = 20.22
d = "some local variable"
# do something
....
...
def somemethod2 (self, arg2):
self.c = "Changed the variable"
# do something 2
...

In such situations, where the Instance variables come into existence
only when they are used
it is very difficult to track the flow of code. Its obviously not
possible to remember what
instance variable was defined where, when reading some substantial
amount of code and where
it was manipulated for that matter. It becomes so very frustrating
even when reading a Class's
code with just 6-8 methods and not more than 100-150 lines of code.

I am interested in knowing if I am reading this kind of code in the
wrong way mostly because
of C++/Java hangover since most other languages follow the same
approach as them? If there
is a Pythonic way reading this code for better readability? What made
Python developers to
adopt this strange strategy keeping "Readibility Counts" in mind?

-- Python Rocks!
Madhusudan.C.S
 
C

Chris Rebert

I am sorry all I am not here to just blame Python. This is just an
introspection of whether
what I believe is right. Being a devotee of Python from past 2 years I
have been writing only
small apps and singing praises about Python where ever I go. I now got
a chance to read
Django's code for some reason. I have now strongly started feeling if
Python really follows its
"Readability Counts" philosophy. For example,

class A:
a = 10
b = "Madhu"

Those are class variables, not instance variables. There is a distinct
difference. Instance variables, in contrast, are "declared" and
created in the body of the __init__ method.
def somemethod(self, arg1):
self.c = 20.22
d = "some local variable"
# do something
....
...
def somemethod2 (self, arg2):
self.c = "Changed the variable"
# do something 2
...

In such situations, where the Instance variables come into existence
only when they are used
it is very difficult to track the flow of code. Its obviously not
possible to remember what
instance variable was defined where, when reading some substantial
amount of code and where
it was manipulated for that matter. It becomes so very frustrating
even when reading a Class's
code with just 6-8 methods and not more than 100-150 lines of code.

That's bad coding style on the part of the code writer.
Conditionally-existing instance variables are *evil*.

Cheers,
Chris
 
C

Carl Banks

  I am sorry all I am not here to just blame Python. This is just an
introspection of whether
what I believe is right. Being a devotee of Python from past 2 years I
have been writing only
small apps and singing praises about Python where ever I go. I now got
a chance to read
Django's code for some reason. I have now strongly started feeling if
Python really follows its
"Readability Counts" philosophy.

Ok, let's clear up a couple misconceptions right now.

First: "Reabability counts" is a design principle of the language, not
a mandate that every Python programmer write readable code. If Django
project wants to write less readable code, they can. "Readability
counts" means that Python is designed to make it easy for the Django
project to write readable code if they choose.

Second: "Readability counts" is not the only factor that matters.
There are 19 Zen and sometimes they conflict with each other. Python
tries to balance all these concerns; it doesn't greedily maximize any
one of them. Some things might be a little less readable than they
could be, but oftentimes that's because other design considerations
were judged more important in that case.

Having cleared that up, let's consider your specific points.
For example,

    class A:
    a = 10
    b = "Madhu"

    def somemethod(self, arg1):
        self.c = 20.22
        d = "some local variable"
        # do something
        ....
    ...
    def somemethod2 (self, arg2):
        self.c = "Changed the variable"
        # do something 2
        ...

In such situations, where the Instance variables come into existence
only when they are used
it is very difficult to track the flow of code. Its obviously not
possible to remember what
instance variable was defined where, when reading some substantial
amount of code

Impossible? You are seriously overstating things.
and where
it was manipulated for that matter.

This criticism is completely unfair. Instance variables have to be
manipulated somewhere, and unless your object is immutable, that is
going to happen outside of __init__. That's true in Java, C++, and
pretty much any other language.
It becomes so very frustrating
even when reading a Class's
code with just 6-8 methods and not more than 100-150 lines of code.

I am interested in knowing if I am reading this kind of code in the
wrong way mostly because
of C++/Java hangover since most other languages follow the same
approach as them?

(Well, it is a lot safer in Python. In C++, if you try to use an
uninitialized variable, you'd get undefined behavior. Better to
initialize it to null and at least get a guaranteed segfault. In
Python if you try to use an uninitialized variable you'd get an
AttributeError.)
If there
is a Pythonic way reading this code for better readability? What made
Python developers to
adopt this strange strategy keeping "Readibility Counts" in mind?

I'm not going to argue that this doesn't hurt readability, because it
does (though not nearly as much as you claim). But there are other
considerations, and in this case the flexibility of defining
attributes outside __init__ is worth the potential decrease in
readability.

Here's a couple examples of where it's useful:

1. Sometimes classes are initialized without calling __init__, strange
but true. Look at the pickle module, for instance. It creates the
object without calling __init__ then sets all the instance variables
itself.

2. Some classes have factory classmethods that create the object by a
different means than calling __init__. (In fact, some classes have an
internal method that initializes variables, which __init__ and the
factories both call.)

3. Some objects, such as proxies, have uncertain sets of attributes.
Requiring such objects to predict all the variables they need inside
__init__ would limit their usability. Programmers would probably
resort to hack-like workarounds like Decorator Design Pattern.

4. It allows a simplification of the object system. Right now, all
attribute access is handled identically: at any point in time any
attribute may be set. If you wanted to limit attribute creation to
__init__, then it would mean objects have to have two phases (one,
during __init__, where you can create attributes; the other, where you
can only modify them). This would add significant complexity, and
"Simple is better than complex.

I'll also point out that even in C++/Java/etc. you have occasions
where an instance variable doesn't do anything until a something
triggers it. You can look to the constructor to see what it's
initialized to, but it isn't really initialized to anything useful
until some method call sets it to something useful. (Python makes it
explicit that the variable isn't in use until it is initialized, and
"Explicit is better than implicit".)


In the end, the answer to you question is simply, "Readibilty counts,
but other stuff matters too."


Carl Banks
 
P

Paul Rubin

Carl Banks said:
and where it was manipulated for that matter.

This criticism is completely unfair. Instance variables have to be
manipulated somewhere, and unless your object is immutable, that is
going to happen outside of __init__. That's true in Java, C++, and
pretty much any other language.

The criticism is very valid. Some languages do support immutable
variables (e.g. "final" declarations in Java, "const" in C++, or
universal immutability in pure functional languages) and they do so
precisely for the purpose of taming the chaos of uncontrolled
mutation. It would be great if Python also supported immutability.
I'm not going to argue that this doesn't hurt readability, because it
does (though not nearly as much as you claim). But there are other
considerations, and in this case the flexibility of defining
attributes outside __init__ is worth the potential decrease in
readability.

There are cases where this is useful but they're not terribly common.
I think it would be an improvement if creating new object attributes
was by default not allowed outside the __init__ method. In the cases
where you really do want to create new attributes elsewhere, you'd
have to explicitly enable this at instance creation time, for example
by inheriting from a special superclass:

class Foo (DynamicAttributes, object): pass
4. It allows a simplification of the object system. Right now, all
attribute access is handled identically: at any point in time any
attribute may be set. If you wanted to limit attribute creation to
__init__, then it would mean objects have to have two phases (one,
during __init__, where you can create attributes; the other, where you
can only modify them). This would add significant complexity, and
"Simple is better than complex.

On the other hand, correct is better than buggy.
 
C

Carl Banks

That's bad coding style on the part of the code writer.
Conditionally-existing instance variables are *evil*.


Do you mean conditionally-useful instance variables evil, or that
conditionally-useful variables are ok but it's evil for them to
conditionally-exist?

The former I don't agree with at all.

If it's the latter, I believe there is something to be said for
variables that exist when they are needed and don't when they're not.
However, I acknowledge that listing all the variables you intend to
use in __init__ is highly comforting, even if it does belie their
current uselessness.


Carl Banks
 
C

Carl Banks

The criticism is very valid.  Some languages do support immutable
variables (e.g. "final" declarations in Java, "const" in C++, or
universal immutability in pure functional languages) and they do so
precisely for the purpose of taming the chaos of uncontrolled
mutation.  It would be great if Python also supported immutability.

I don't think what you said (which is fine) makes his criticism valid,
unless you also suggest that all objects should be immutable.

If any objects are mutable, you have to be prepared for objects to
mutated outside the initializer.

I guess it could be a valid criticism of Django's progamming style
("They mutate stuff haphazardly everywhere!!~") but that's not the
Python langauge's problem.

On the other hand, correct is better than buggy.

What bugginess do you speak of? Adding a variable outside __init__ is
questionable style, not a bug. I don't think it's a significant
source of bugs, either. And I definitely don't think the language
should add a layer of complexity (and I think you're underestimating
the complexity of making the whole object system two-phase) just to
prevent a minor style issue.

pychecker is the appropriate tool to diagnose this issue.


Carl Banks
 
P

Paul Rubin

Carl Banks said:
I don't think what you said (which is fine) makes his criticism valid,
unless you also suggest that all objects should be immutable.

It would be enough to have a way to make specific objects and instance
attributes immutable.
If any objects are mutable, you have to be prepared for objects to
mutated outside the initializer.

Sure, but why have mutable objects all over the place? And, why
always have attributes visible at all, outside the class definition?
The approach in C++ and Java is to have public and private instance
variables, where the private ones are visible only in the class methods.
 
C

Chris Rebert

Do you mean conditionally-useful instance variables evil, or that
conditionally-useful variables are ok but it's evil for them to
conditionally-exist?

The former I don't agree with at all.

If it's the latter, I believe there is something to be said for
variables that exist when they are needed and don't when they're not.
However, I acknowledge that listing all the variables you intend to
use in __init__ is highly comforting, even if it does belie their
current uselessness.

The latter. I never even used the word "useful", so I have no idea
where you got that from.
To reiterate, variables which might only exist under certain
conditions are evil, IMHO.
This is not to say they are not useful in certain specific cases, just
that in general there are better ways to design/structure programs to
avoid them.

Cheers,
Chris
 
M

Mark Wooding

[Dynamically adding and removing instance attributes...]
Here's a couple examples of where it's useful:

1. Sometimes classes are initialized without calling __init__, [...]
2. Some classes have factory classmethods [...]
3. Some objects, such as proxies, have uncertain sets of attributes. [...]
4. It allows a simplification of the object system. [...]

All good points. I'd like to add a fifth: it can provide a handy hook
for one module to hang data off of another module's objects. This sort
of thing isn't very nice, but it's sometimes the best way.

(Other approaches include using wrappers, which might be very invasive
if opaque, or don't actually solve the name-collision problem if
transparent; or maintaining a dictionary, which involves messing about
with weak pointers if you want to keep it from filling with cruft.)
In the end, the answer to you question is simply, "Readibilty counts,
but other stuff matters too."

Amen.

-- [mdw]
 
C

Chris Rebert

Carl Banks <[email protected]> writes:

Sure, but why have mutable objects all over the place? And, why
always have attributes visible at all, outside the class definition?
The approach in C++ and Java is to have public and private instance
variables, where the private ones are visible only in the class methods.

Regarding the second question, Python adheres to the principle that
"We're all consenting adults here" and so does not provide complete
encapsulation like more B&D languages. This is handy sometimes as it
allows one to break through the encapsulation when necessary and
fiddle with the internals. This is not to say that breaking
encapsulation willy-nilly is advised, but it does allow for some neat
hackery every now and again.

Cheers,
Chris
 
R

Roy Smith

"Madhusudan.C.S said:
In such situations, where the Instance variables come into existence
only when they are used it is very difficult to track the flow of code.

As the saying goes, "It's possible to write Fortran in any language".

My personal habit is to "declare" all instance variables in the __init__()
method of every class. If there's no better value, I set them to None.
This isn't strictly required, but I think it makes it easier for somebody
reading the code to understand the class.

I'm not a big fan of dogmatic rules, other than the rule that says you
should make your code as easy for somebody else to understand as possible.
 
C

Carl Banks

The latter. I never even used the word "useful", so I have no idea
where you got that from.

I was just asking for clarification of your rationale, of which I
imagined two possibilities ("conditional-usefulness is bad", or
"declaring attributes outside of __init__" is bad). However...

To reiterate, variables which might only exist under certain
conditions are evil, IMHO.
This is not to say they are not useful in certain specific cases, just
that in general there are better ways to design/structure programs to
avoid them.

....the way you phrase this suggests to me that conditional-usefulness
*is* what you are really concerned with. Let me give you an example:

class A:
def __init__(self):
pass
def something(self):
self._conditionally_existent_variable = 1


ISTM that I don't need any restructuring at all to avoid conditional
existence; all I'd have to do is add

self._conditionally_existent_variable = None

to __init__. If you think I need to restructure this code, than you
evidently care about something more than just conditional existence.
If so, what is it that's so evil about conditionally-existent
variables? (I'll leave the question open-ended this time.)


Carl Banks
 
R

r

As the saying goes, "It's possible to write Fortran in any language".

My personal habit is to "declare" all instance variables in the __init__()
method of every class.  If there's no better value, I set them to None.  
This isn't strictly required, but I think it makes it easier for somebody
reading the code to understand the class.

I'm not a big fan of dogmatic rules, other than the rule that says you
should make your code as easy for somebody else to understand as possible..

Roy i totally agree and as i read down this thread i was thinking i
might get to spit that out first but you beat me -- Darn!

PS: your explanation is also much more eloquent than mine would have
been :)
 
P

Paul Rubin

Carl Banks said:
If so, what is it that's so evil about conditionally-existent
variables? (I'll leave the question open-ended this time.)

I have found they make the code more confusing and bug prone.
It's better to define and document all the instance variables
in one place, in most cases.
 
C

Carl Banks

It would be enough to have a way to make specific objects and instance
attributes immutable.

Enough for what, to make the guy's criticism valid? No it wouldn't.
Or are you just ignoring the OP altogether and complaining about what
bothers you?

For my part I am concerned with answering the OP's issues here, not
yours.

Sure, but why have mutable objects all over the place?  And, why
always have attributes visible at all, outside the class definition?
The approach in C++ and Java is to have public and private instance
variables, where the private ones are visible only in the class methods.

The OP wasn't complaining about the fact that objects aren't
immutable, as far as I can tell, nor about having public and private
variables, so I can't agree Python's lack of these has anything to do
with the OP's concerns.


Carl Banks
 
C

Carl Banks

I have found they make the code more confusing and bug prone.
It's better to define and document all the instance variables
in one place, in most cases.

That means all I have to do is add a stopgap value in __init__. I'm
asking Chris why that is evidently not enough, and that I'd have to
structure/design my code to avoid it.


Carl Banks
 
R

Roy Smith

Chris Rebert said:
This is not to say that breaking encapsulation willy-nilly is advised,
but it does allow for some neat hackery every now and again.

I'm all for neat hackery, except when used in code that I need to read and
understand.
 
C

Chris Rebert

I was just asking for clarification of your rationale, of which I
imagined two possibilities ("conditional-usefulness is bad", or
"declaring attributes outside of __init__" is bad). However...



...the way you phrase this suggests to me that conditional-usefulness
*is* what you are really concerned with. Let me give you an example:

class A:
def __init__(self):
pass
def something(self):
self._conditionally_existent_variable = 1


ISTM that I don't need any restructuring at all to avoid conditional
existence; all I'd have to do is add

self._conditionally_existent_variable = None

to __init__. If you think I need to restructure this code, than you
evidently care about something more than just conditional existence.

No, this is exactly the sort of restructuring I was referring to; and
nothing more. Making that one-line change would be sufficient for me.
Perhaps "restructuring" was a poor choice of words...

Cheers,
Chris
 
B

bieffe62

The criticism is very valid.  Some languages do support immutable
variables (e.g. "final" declarations in Java, "const" in C++, or
universal immutability in pure functional languages) and they do so
precisely for the purpose of taming the chaos of uncontrolled
mutation.  It would be great if Python also supported immutability.


There are cases where this is useful but they're not terribly common.
I think it would be an improvement if creating new object attributes
was by default not allowed outside the __init__ method.  In the cases
where you really do want to create new attributes elsewhere, you'd
have to explicitly enable this at instance creation time, for example
by inheriting from a special superclass:

   class Foo (DynamicAttributes, object): pass

You cannot do that, but you can establish a fixed set of attributes by
defining
the __slot__ class variable.


Ciao
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top