dangerous class neighborhood

Avi Gross avigross at verizon.net
Thu Dec 27 16:45:59 EST 2018


There have been several discussions about unexpected behavior when people
write programs within a class definition. Some suggest various ways around
the problem and that is fine although I bet new people will keep
encountering this issue.

 

I have some questions about what people want to do with this kind of
programming and for each use, there may be suggested ways to get things done
and get the right result in a reasonably natural way.

 

I have some overall concerns regarding the aggressive approach taken in
Python toward supporting too many features in what is loosely considered
object-oriented programming to the point where some combinations interact in
mathematically intractable ways resulting in concessions and compromises
such as the one we see.

 

Question 1: Do you want evaluation at class definition time or at run time?

 

Question 2: Do you want the variables available at the class level or at the
instance level?

 

Question 3: Which python variations on syntactic sugar, such as list
comprehensions, get expanded invisibly in ways that make the problem happen
by asking for variables to be found when no longer in the visible range?

 

There may be matters of efficiency some would consider but some of the
examples seen recently seemed almost silly and easy to compute. The people
asking about this issue wanted to define a bunch of CONSTANTS, or things
that might as well be constants, like this:

 

def Foo():

                A = ("male", "female", "other")

                B = [ kind[0] for kind in A ]            # First letters
only

                # And so on making more constants like a dictionary mapping
each string to a number or vice versa.

 

All the above can be evaluated at the time the class is defined but
unintuitive scope rules make some operations fail as variables defined in
the scope become unavailable to other things that SEEM to be embedded in the
same scope.

 

So, are these constants being called as Foo.A or are they used after an
instance is created like:

 

Aleph = Foo()

# Use Aleph.A 

 

If they are ONLY to be used within an instance of Foo or invoked from within
there, there may be a fairly simple suggestion. If you already have a
__init__ method, then instantiate the variables there carefully using the
self object to reference those needed.

 

def Foo():

                def __init__(self):

                                # calculate A and B and whatever.

                                self.A = A

self.B = B

# .

These calculations would not be at declaration time for class Foo but the
results would be created every time an object of class Foo came along. If
Foo is subclassed, it would be wise to make sure the initialization reaches
up to run Foo.__init__ properly too.

 

Within __init__ I presume all the USUAL scoping rules make sense so if
making a B includes using A, it would easily be found there first. Again,
not self.A, just A at that stage. You can create self.A near the end of the
initialization when all calculations needed are complete.

 

Second suggestion, even if what is being made may not be constants. 

 

Create a function either outside the class or defined within. Have it do any
internal calculations you need in which all internal variables can play
nicely with each other. Then let it return all the variables in a tuple like
this:

 

def make_sexual_constants():

                A = .

                B = .

                C = f(A,B)

                D = .

def Foo():

                (A, B, C, D) = make_sexual_constants():

 

Can we agree that the class Foo now has those 4 variables defined and
available at either the class level or sub-class or instance levels? But the
values are created, again, in a unified safe environment?

 

As noted in section 3, it would be good to know what python features may be
unsafe in this kind of context. I had an unrelated recent discussion where
it was mentioned that some proposed feature changes might not be thread
safe. Valid consideration when that may lead to hard-to-explain anomalies.  

 

We now hear that because a list comprehension can be unwound internally into
a "while" loop and an "if" statement and that some parts may expand to calls
to a "range" statement, perhaps some variables are now in more deeply
embedded contexts that have no access to any class variables. I think that
is quite reasonable; hence my suggestion we need to know which ones to
avoid, or use a workaround like expanding it out ourselves and perhaps
carefully import variables into other contexts such as by passing the
variable into the function that otherwise cannot access it from a point it
can still be seen.

 

Least, but at least last, I ask if the need really exists for these
variables as constants versus functions. If creating C this way runs into
problems, but A and B are fine, consider making a method with some name like
Foo.get_C() that can see A and B and do the calculation and yet return the
value of C needed. Less efficient but .

 

I apologize for the length but assure you I normally would say much more.

 

Avi

 

 

 

 




More information about the Python-list mailing list