Circular Class Logic

H

half.italian

I have a set of classes that describe Files, Folders, etc., that I use
often in my scripts for moving files around, getting a files
extension, converting paths, changing permissions, etc It's very
similar to Jason Orendorff's 'Path' library, and is very useful to
me. The base class 'Data.py' stores all the basic attributes of any
piece of data on disk, and it is then subclassed to represent files
and folders with the attributes specific to that type of object.

I recently made a new class 'Disk.py' that is a subclass of
'Folder.py', to describe all of the attributes of any local or network
disks attached to the computer. I subclassed it from Folder, because
it really is a folder, it's just at the root of a drive. I'm
realizing that these 'Disk' objects are pretty useful, and I came up
with the idea to put a 'Disks' instance as a shared class variable
(singleton?) within the base class ('Data') The idea being that any
instance of a File, or Folder object would contain the list of Disk
objects attached to the system, and could use them accordingly. Also
important is that I would only need to gather the disk info once for
any given running application and all the instances could use it
(origianlly I was getting the disk info for each File/Folder object)

After an hour of banging my head from getting "AttributeError:
'module' object has no attribute 'Disks'" errors, I finally realized
that I'm trying to include an instance in the base class that is a
subclass of itself. Short of making 'Disk' no longer a subclass of
Folder, is there any other way to include a subclassed instance in the
base class of that object? (this is very hard to put in to words)

~Sean
 
B

Ben Finney

Short of making 'Disk' no longer a subclass of Folder, is there any
other way to include a subclassed instance in the base class of that
object? (this is very hard to put in to words)

It's a little difficult to visualise what you're describing, but IIUC
your problem is of this form:

===== foo.py =====
import bar

class Foo(bar.Bar):
pass
=====

===== bar.py =====
import baz

class Bar(baz.Baz):
pass
=====

===== baz.py =====
import foo

class Baz(foo.Foo):
pass
=====

That is, each of the classes want to inherit from the others.

The usual solution in these cases is to find the common required
functionality and factor that out to a separate class that is then the
superclass of two of the existing classes, breaking the circle.

===== wibble.py =====
# no dependencies

class Wibble(object):
pass
=====

===== foo.py =====
import wibble
import bar

class Foo(wibble.Wibble, bar.Bar):
pass
=====

===== baz.py =====
import wibble

class Baz(wibble.Wibble):
pass
=====

Note that Baz no longer subclasses foo.Foo, and both Foo and Baz get
the common functionality they share from wibble.Wibble.
 
H

half.italian

That is, each of the classes want to inherit from the others.

That's not exactly what I'm doing, but your comment still might help.
I actually want to include an instance of a subclass in it's
superclass like this:

===== foo.py =====
import Baz

class Foo:
baz = Baz.Baz()

def __init__(self):
pass
=====

===== bar.py =====
import Foo

class Bar(Foo.Foo):
pass
=====

===== baz.py =====
import Bar

class Baz(Bar.Bar):
pass
=====
The usual solution in these cases is to find the common required
functionality and factor that out to a separate class that is then the
superclass of two of the existing classes, breaking the circle.

===== wibble.py =====
# no dependencies

class Wibble(object):
pass
=====

===== foo.py =====
import wibble
import bar

class Foo(wibble.Wibble, bar.Bar):
pass
=====

===== baz.py =====
import wibble

class Baz(wibble.Wibble):
pass
=====

Note that Baz no longer subclasses foo.Foo, and both Foo and Baz get
the common functionality they share from wibble.Wibble.

I have to think about that for a bit and see if it makes sense to
factor anything out. Thanks for the idea. Would that be the best
solution considering the above description?

~Sean
 
G

Gabriel Genellina

That's not exactly what I'm doing, but your comment still might help.
I actually want to include an instance of a subclass in it's
superclass like this:

Move the initialization to another function.
===== foo.py =====
# import Baz Remove the line above

class Foo:
# baz = Baz.Baz() Remove the line above

def __init__(self):
pass
=====

and add this below:
def initFoo():
import baz
Foo.baz = baz.Baz()
initFoo()

No changes in bar.py, baz.py
Instead of initFoo, you could use a custom metaclass. Or a class decorator (if
and when they become available...)
The code above is an effective way of doing what you want. But I'd think about
the actual need of doing such things - are you sure it's a good design?
 
H

half.italian

Remove the line above
and add this below:
def initFoo():
import baz
Foo.baz = baz.Baz()
initFoo()

I got it to work, but I had to add a check to see if the class
variable had been set..

def initBaz():
import Baz
Foo.baz = Baz.Baz()

class Foo:
baz = None
def __init__(self):
if Foo.baz == None:
Foo.baz = True
initBaz()

What exactly is being accomplished by having the init function outside
of the class? If there is no check, wouldn't it just execute every
time an object is instantiated anyway?
Instead of initFoo, you could use a custom metaclass. Or a class decorator (if
and when they become available...)

I haven't tried either of those yet. The names scare me. :) Sounds
like this might be a good time to explore them.
The code above is an effective way of doing what you want. But I'd think about
the actual need of doing such things - are you sure it's a good design?

I thought it was a good design, but now I'm not so sure. I'm
untrained, so often I dont know the right way to do things. As I
build this into the libraries, I'll keep an eye on how the code is
developing and go from there. Thanks for the help!

~Sean
 
P

Paul McGuire

I got it to work, but I had to add a check to see if the class
variable had been set..

def initBaz():
import Baz
Foo.baz = Baz.Baz()

class Foo:
baz = None
def __init__(self):
if Foo.baz == None:
Foo.baz = True
initBaz()

What exactly is being accomplished by having the init function outside
of the class? If there is no check, wouldn't it just execute every
time an object is instantiated anyway?


I haven't tried either of those yet. The names scare me. :) Sounds
like this might be a good time to explore them.


I thought it was a good design, but now I'm not so sure. I'm
untrained, so often I dont know the right way to do things. As I
build this into the libraries, I'll keep an eye on how the code is
developing and go from there. Thanks for the help!

~Sean

Just initialize Folder at module level - see below.
-- Paul

class Folder(object):
def __init__(self,path):
self.path = path
pass

def __repr__(self):
return self.__class__.__name__+"("+self.path+")"

class Disk(Folder):
def __init__(self,driveLetter):
super(Disk,self).__init__(driveLetter+":/")

# hokey function to test if a drive letter points to a drive, I'm
# sure there's a better way
import os,string
def isDisk(d):
try:
os.stat(d + ":/")
return True
except:
return False

# instead of calling some initFolder() method, just include this
statement
# at module level in the module that declares both classes
Folder.allDisks = map(Disk,[d for d in string.uppercase if isDisk(d)])

# now can reference allDisks through Folder instances
f = Folder("C:/temp")
print f.allDisks


prints:
[Disk(C:/), Disk(D:/), Disk(E:/)]
 
H

half.italian

Just initialize Folder at module level - see below.
-- Paul

class Disk(Folder):
def __init__(self,driveLetter):
super(Disk,self).__init__(driveLetter+":/")

What is going on there? Is it just explicitly calling the super's
init function? How is that really different from this:

class Disk(Folder):
def __init__(self,driveLetter):
Folder.Folder.__init__(self.path) # ??? Being that Folder is the
superclass?

I'd like to be able to use the shared Disk objects in any class that
is a subclass of Data. Will the above method still make the Disk
objects available in File.py? Also, what if the classes are in
different modules.

I'll try to experiment with it this afternoon. I'm using more
information than just the drive letter. What if the user wants to
find out the unc path to the share given the drive letter, or copy a
file to the local disk that is not a system disk that has the most
available space on it, or wants to mount or unmount a network drive.

pause...

I'm trying to justify why the Folders/Files really need the drive info
beyond just the paths and I'm having a hard time. I even just tried
to give an example of how it would be used, and couldn't. It seems to
make sense on a gut level, but maybe it's just as usefull on it's
own. There's definitely justification for having Disks be a subclass
of Folder, but not necessarily for there to be an instance of it
available within the classes. All that kind of work will be external
to the class.

I think I'll backtrace and keep it separate. Thanks for letting me
think it through.

~Sean
 
P

Paul McGuire

What is going on there? Is it just explicitly calling the super's
init function?

What is going on is that Disk is being initialized with a single
character, such as 'D', and then calling the superclass with 'D:/',
the root folder on D. You're the one who said that Disk was a
subclass of Folder (sorry, you called it "Data"), so I tried to come
up with an example in which this made some kind of sense.
How is that really different from this:

class Disk(Folder):
def __init__(self,driveLetter):
Folder.Folder.__init__(self.path) # ??? Being that Folder is the
superclass?
Where did self.path come from? Even though Folder is the superclass,
self.path doesn't exist until the Folder.__init__ method gets called.
This ain't C++ you know. And as I said above, we are taking a single
character drive letter, and calling the superclass init with the root
directory of that drive, so these are .
I'd like to be able to use the shared Disk objects in any class that
is a subclass of Data. Will the above method still make the Disk
objects available in File.py? Also, what if the classes are in
different modules.

I'll try to experiment with it this afternoon. I'm using more
information than just the drive letter. What if the user wants to
find out the unc path to the share given the drive letter, or copy a
file to the local disk that is not a system disk that has the most
available space on it, or wants to mount or unmount a network drive.
Knock yourself out. I just hacked together these Folder/Disk classes
by way of a half-baked-but-working example. My point is that to
initialize a class level variable on class Foo, all that is needed is
to assign it in the defining module, that is:

class Foo:
pass

Foo.fooClassVar = "a class level variable"

Now any Foo or sub-Foo can access fooClassVar. The type of
fooClassVar can be anything you want, whether it is a Foo, subclass of
Foo or whatever, as long as it has been defined by the time you assign
fooClassVar.

-- Paul
 
G

Gabriel Genellina

I got it to work, but I had to add a check to see if the class
variable had been set..
class Foo:
baz = None
def __init__(self):
if Foo.baz == None:
Foo.baz = True
initBaz()

This is a different approach. You are using instance initialization
(Foo.__init__) to finish class initialization. Foo.__init__ will be called
each time you create a Foo instance; that's why you had to check if
Foo.baz is None.
(BTW, Foo.baz=True is unneeded).
The only problem I can see is that the Foo class is not completely
initialized until its first instance is created; this may not be a problem
in your case.
What exactly is being accomplished by having the init function outside
of the class? If there is no check, wouldn't it just execute every
time an object is instantiated anyway?

What I intended was different:

==========
C:\TEMP>type foo.py
class Foo:
baz = None

def __init__(self):
pass

def initFoo():
import baz
Foo.baz = baz.Baz()
initFoo()

C:\TEMP>type bar.py
import foo

class Bar(foo.Foo):
pass

C:\TEMP>type baz.py
import bar

class Baz(bar.Bar):
pass

C:\TEMP>type test.py
import foo,bar,baz

print foo.Foo.baz
f = foo.Foo()
print f.baz
b = bar.Bar()
print b.baz
print baz.Baz.baz

C:\TEMP>python test.py
<baz.Baz instance at 0x00AD7B20>
<baz.Baz instance at 0x00AD7B20>
<baz.Baz instance at 0x00AD7B20>
<baz.Baz instance at 0x00AD7B20>
==========

initFoo is only executed when Foo is imported the first time. Note that
you can't do that if
I thought it was a good design, but now I'm not so sure. I'm
untrained, so often I dont know the right way to do things. As I
build this into the libraries, I'll keep an eye on how the code is
developing and go from there. Thanks for the help!

Usually Python code is quite concise and straightforward; when I find
myself writing things too convoluted, I know it's time to
refactor/redesign. (Anyway some things remain not as simple as I'd like
:( )
Perhaps you later find the need for a different/better way.
 
H

half.italian

How is that really different from this:
Where did self.path come from? Even though Folder is the superclass,
self.path doesn't exist until the Folder.__init__ method gets called.
This ain't C++ you know.

My bad. I should have said:

class Disk(Folder):
def __init__(self,driveLetter):
Folder.Folder.__init__(self, driveLetter + ":/")
Knock yourself out. I just hacked together these Folder/Disk classes
by way of a half-baked-but-working example. My point is that to
initialize a class level variable on class Foo, all that is needed is
to assign it in the defining module, that is:

class Foo:
pass

Foo.fooClassVar = "a class level variable"
Now any Foo or sub-Foo can access fooClassVar. The type of
fooClassVar can be anything you want, whether it is a Foo, subclass of
Foo or whatever, as long as it has been defined by the time you assign
fooClassVar.

That was the first thing I tried, but because the libs were importing
each other, etc, it got hung up with wierd "This module doesn't exist"
errors, when it clearly did.

~Sean
 
P

Paul McGuire

class Disk(Folder):
def __init__(self,driveLetter):
Folder.Folder.__init__(self, driveLetter + ":/")

Google for "python super" - there is documentation on this, plus some
interesting c.l.py threads and blog scribblings on the behavior and
good/bad aspects of super. You may be better off using the explicit
class calls as you have. I always assumed that super, being added to
the language later, represented some form of improvement, but this may
not be 100% correct.

-- Paul
 
G

greg

Paul said:
I always assumed that super, being added to
the language later, represented some form of improvement, but this may
not be 100% correct.

It's not -- super is not a replacement for explicit
inherited method calls. It does something different,
and has different use cases.

It's usually not appropriate for __init__ calls,
because an __init__ method generally doesn't have
the same signature as that of its base class(es).
You're better off calling the base __init__
directly, then you know exactly which method
you're calling and what signature it has.
 

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

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top