Addind imports to a class namespace

R

Ryan K

Greetings,

In order to avoid circular imports, I have a class that I want to
improve upon:

Class GenerateMenuXhtml(threading.Thread):
"""
Subclasses a threading.Thread class to generate a menu's XHTML in
a separate
thread. All Link objects that have this menu associated with it
are gathered
and combined in an XHTML unordered list.

If the sender is of type Link, then all menus associated with that
link are
iterated through and rebuilt.
"""
def __init__(self, instance):
from asqcom.apps.staticpages.models import Menu, Link
self.Link = Link
self.Menu = Menu

As you can see I just expose these imports by attaching them to
members of the class. There must be "prettier" option though where I
can just add these imoprts to the class's namespace so all methods of
any instance will have access to the imported modules.

How would I go about doing this? How can I access the namespace of any
class? Through Class.__dict__?

Thanks,
Ryan
 
P

Peter Otten

Ryan said:
In order to avoid circular imports, I have a class that I want to
improve upon:

Class GenerateMenuXhtml(threading.Thread):
"""
Subclasses a threading.Thread class to generate a menu's XHTML in
a separate
thread. All Link objects that have this menu associated with it
are gathered
and combined in an XHTML unordered list.

If the sender is of type Link, then all menus associated with that
link are
iterated through and rebuilt.
"""
def __init__(self, instance):
from asqcom.apps.staticpages.models import Menu, Link
self.Link = Link
self.Menu = Menu

As you can see I just expose these imports by attaching them to
members of the class. There must be "prettier" option though where I
can just add these imoprts to the class's namespace so all methods of
any instance will have access to the imported modules.

How would I go about doing this? How can I access the namespace of any
class? Through Class.__dict__?

You can write either

class A(object):
from some.module import Menu, Link

or

class A(object):
@classmethod
do_imports(cls):
from some.module import Menu, Link
cls.Menu = Menu
cls.Link = Link

A.do_imports()

to put the names into the class. The first form performs the import while
the containing module is imported and therefore won't help with breaking
circles. In the second form you have to defer the A.do_imports() method call
until the import from some.module is safe.

But I still recommend that you have another look at your package
organization to find a way to avoid circles.

Peter
 
R

Ryan K

Thanks for your help Peter.

I'm thinking that perhaps this isn't a circular import and that I
don't understand importing. Here is a better explanation of my case (I
am using Django):

I have file x.py that declares classes A, B, C.

There is also a file y.py that contains two methods T, U and the class
that we are talking about above.

x.py uses a dispatcher system to connect a signal to methods T and U
in y.py so it does: from y import T, U.

y.py needs to use classes A, B, C which is basically Menu and Link
(and some other class) above so I am thinking that if in y.py I have
from x import A, B, C that will cause a circular import?

Is this not correct and if it isn't can you explain why? Does using
from ... import X, Y, Z, i.e. explicit imports avoid this problem or
does it exacerbate it?

Thanks,
Ryan
 
P

Peter Otten

Ryan said:
I'm thinking that perhaps this isn't a circular import and that I
don't understand importing. Here is a better explanation of my case (I
am using Django):

I have file x.py that declares classes A, B, C.

Classes in Python are executable code, just like import-statements. That's
why there is no Python equivalent to forward declarations in other
languages; every name has to be known at the point where it occurs in the
module.
There is also a file y.py that contains two methods T, U and the class
that we are talking about above.

x.py uses a dispatcher system to connect a signal to methods T and U
in y.py so it does: from y import T, U.

y.py needs to use classes A, B, C which is basically Menu and Link
(and some other class) above so I am thinking that if in y.py I have
from x import A, B, C that will cause a circular import?

Yes. Having x import y and y import x creates a cycle. If you cannot avoid
this by moving to a simpler design you can always introduce a third module z
that imports x and y, and then explicitly resolves the circular references.
Is this not correct and if it isn't can you explain why? Does using
from ... import X, Y, Z, i.e. explicit imports avoid this problem or
does it exacerbate it?

It has no effect.

from module import X

is equivalent to

import module
X = module.X
del module # just the name, not the module itself

Peter
 
D

Dave Angel

Ryan said:
Thanks for your help Peter.

I'm thinking that perhaps this isn't a circular import and that I
don't understand importing. Here is a better explanation of my case (I
am using Django):

I have file x.py that declares classes A, B, C.

There is also a file y.py that contains two methods T, U and the class
that we are talking about above.

x.py uses a dispatcher system to connect a signal to methods T and U
in y.py so it does: from y import T, U.

y.py needs to use classes A, B, C which is basically Menu and Link
(and some other class) above so I am thinking that if in y.py I have
from x import A, B, C that will cause a circular import?

Is this not correct and if it isn't can you explain why? Does using
from ... import X, Y, Z, i.e. explicit imports avoid this problem or
does it exacerbate it?

Thanks,
Ryan
You indeed have described a circular import. In this case, a mutual
dependency exists between x.py and y.py. This can and nearly always
should be avoided. That's independent of the language involved, in any
language, circular dependencies are a code smell

Now, in Python in particular, first question is whether either x.py or
y.py is your main script module (the one where you do the if __name__ ==
"__main__" and the answer is yes). If so, you're in big trouble, and
you really need to rework it. I could elaborate if needed, but trust
me, you don't want to do this, even if it means writing one more module
containing a single import line and just two or three more, and letting
that be your script.

If that's not the case, next question is whether either module refers to
the other one at import time, or only from inside def and class
definitions. If you have any top-level code going on, other than
imports, and this code actually uses a symbol from the other module,
you're probably in trouble. Not as bad as first case, but still worth
avoiding. Sometimes this can be kludged past, by reordering things a
little bit, maybe by moving one of the imports further down in the file.

Now back to the code smell. If the modules really depend on each other,
you should consider factoring out those dependencies and move them to a
third module that both import. Figure out why the modules are broken up
in the particular way they are, and how you can eliminate circular
references by refactoring. It's hard for me to come up with general
principles, but perhaps an example or three will help.

If a callee needs configuration information stored in globals of the
caller, then you probably need to add that config information as parameters.

If an object needs a reference to functions in another module, add them
when constructing the object, not statically.

Or maybe even have the one module explicitly set global variables in the
other module, so they're in one place, instead of being only in the
namespace of the code initializing them.

That's the best I can do without something concrete to adjust.

DaveA
 
R

Ryan K

Okay so below is the acutal code. I am starting to think there is no
reason why I can't install the post_save signal in signals.py itself
and thereby avoid this issue entirely.

models.py:

class Link(CommonAbstractModel):
...

class Menu(CommonAbstractModel):
....

class StaticPage(CommonAbstractModel):
,,,

class CachedMenuXhtml(CommonAbstractModel):
...

post_save.connect(signals.build_menu, sender=Link)
post_save.connect(signals.build_menu, sender=Menu)

--------------------

# Signlas for caching of menu XHTML

class GenerateMenuXhtml(threading.Thread):

def __init__(self, instance):
from asqcom.apps.staticpages.models import Menu, Link,
CachedMenuXhtml
self.Link = Link
self.Menu = Menu
self.CachedMenuXhtml = CachedMenuXhtml

# Function to run on post_save signal
def build_menu(sender, instance, **kwargs):
GenerateMenuXhtml(instance).start()
 
R

Ryan K

Thank you for your replies. I have factored out the dependency and
everything is solved.

Cheers,
Ryan
 

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,780
Messages
2,569,611
Members
45,283
Latest member
JoannaGrif

Latest Threads

Top