From: Mel on
kj wrote:

> In <mailman.1437.1270163476.23598.python-list(a)python.org> Steve Holden
> <steve(a)holdenweb.com> writes:
>
>>But the real problem is that the OP is insisting on using purely
>>procedural Python when the problem is screaming for an object-oriented
>>answer.
>
> My initial reaction to this comment was something like "What? switch
> from procedural to OO just to be able to do some one-time initialization
> of function-private data???"

Yeah, actually. If the subject had been "Python-style object attributes in
C?" somebody might have suggested C static variables. An example I wrote
lately

volatile static int random_bit ()
{
static unsigned short lfsr = 0xACE1u; // seeded LFSR
// taps: 16 14 13 11; characteristic polynomial: x^16 + x^14 + x^13 +
x^11 + 1
lfsr = (lfsr >> 1) ^ (-(lfsr & 1u) & 0xB400u);
return lfsr & 1;
} // random_bit

(excuse is: this was written for cheap execution in an 8-bit processor.)

This does OK -- but fails the instant I decide that my program needs more
than one pseudo-random bit stream. Then I have the choice of writing
several different random_bit functions, or extending random_bit to take a
pointer to a seeded LFSR provided by the individual caller.

Refactoring the Python function to a Python class, as you mention later,
solves the static-access problem, but that solution is just as vulnerable to
the need-more-than-just-the-one problem as my C function.

Mel.



From: Duncan Booth on
kj <no.email(a)please.post> wrote:

> I suppose one could refactor this:
>
><procedural>
> def spam(x, y, z):
> try:
> mongo = spam.mongo
> except AttributeError:
> mongo = spam.mongo = heavy_lifting_at_runtime()
> return frobnicate(x, y, z, mongo)
>
> ham = spam(3, 4, 5)
></procedural>
>
> into this:
>
><OO>
> class _Spam(object):
> @classmethod
> def _(cls, x, y, z):
> try:
> mongo = cls.mongo
> except AttributeError:
> mongo = cls.mongo = heavy_lifting_at_runtime()
> return frobnicate(x, y, z, mongo)
>
> ham = _Spam._(1, 2, 3)
></OO>
>
>
> Is this really more natural or more readable? Hmmm.

No, but that's because it is needlessly obfuscated. What's with the weird _
method? Why use a class method? Why not just create an instance?

class Spam(object):
mongo = None
def __call__(self, x, y, z):
if self.mongo is None:
self.mongo = heavy_lifting_at_runtime()
return frobnicate(x, y, z, self.mongo)
spam = Spam()

ham = spam(1, 2, 3)


That's natural and readable.

There's also another good reason why the class is better than the static
variable: you can construct multiple different instances with different
calls to 'heavy_lifting_at_runtime'. e.g. You could write a unit test where
mongo is initialised to mock_heavy_lifting_at_runtime().
From: Ethan Furman on
kj wrote:
> <OO>
> class _Spam(object):
> @classmethod
> def _(cls, x, y, z):
> try:
> mongo = cls.mongo
> except AttributeError:
> mongo = cls.mongo = heavy_lifting_at_runtime()
> return frobnicate(x, y, z, mongo)
>
> ham = _Spam._(1, 2, 3)
> </OO>
>
>
> Is this really more natural or more readable? Hmmm.

For this type of situation, my preference would be:

class spam(object):
def __call__(self, x, y, z):
try:
mongo = self.mongo
except AttributeError:
mongo = self.mongo = heavy_lifting_at_runtime()
return frobnicate(x, y, z, mongo)
spam = spam()


No extra objects, out-of-place underscores, etc.

~Ethan~
From: Patrick Maupin on
On Apr 2, 1:21 pm, Ethan Furman <et...(a)stoneleaf.us> wrote:
> For this type of situation, my preference would be:
>
> class spam(object):
>      def __call__(self, x, y, z):
>          try:
>              mongo = self.mongo
>          except AttributeError:
>              mongo = self.mongo = heavy_lifting_at_runtime()
>          return frobnicate(x, y, z, mongo)
> spam = spam()
>
> No extra objects, out-of-place underscores, etc.
>
> ~Ethan~

Well, I'm not a big fan of unnecessary try/except, so I would at least
change it to:

class spam(object):
def __getattr__(self, name):
if name != 'mongo':
raise AttributeError
self.mongo = heavy_lifting_at_runtime()
return self.mongo
def __call__(self, x, y, z):
return frobnicate(x, y, z, self.mongo)
spam = spam()

Regards,
Pat
From: Steven D'Aprano on
On Fri, 02 Apr 2010 16:08:42 +0000, kj wrote:

> Other responses advocated for global variables. I avoid them in
> general,

In general this is wise, but remember that because Python globals are not
globally global, but local to a single module, they're safer than globals
in other languages. Still, it's better to avoid them when possible.


> and doubly so in Python, because I find Python's shenanigans
> with globals mystifying (this business of becoming silently local if
> assigned to);

Globals don't become local when assigned to. You can shadow a global with
a local of the same name, but the global remains untouched:

>>> myglobal = 42
>>> def test():
.... myglobal = 0 # shadow the global with a new local
....
>>> test()
>>> myglobal
42

I find this behaviour perfectly natural, and desirable: it means I can
assign to locals without worrying whether or not I'm about to stomp all
over a global and destroy it. The alternative behaviour would be
disastrous:

>>> def f(x): return x+1
....
>>> def test():
.... f = 'spam'
....
>>> test()
>>> f(2) # this doesn't happen
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable



--
Steven