CodeGym /Java Course /Python SELF EN /Standard Errors, Part 3

Standard Errors, Part 3

Python SELF EN
Level 20 , Lesson 3
Available

9.1 Creating Cyclical Module Dependencies

Let's say you have two files, a.py and b.py, each importing the other like this:

In a.py:


import b

def f():
    return b.x

print(f())

In b.py:


import a

x = 1

def g():
    print(a.f())

First, let's try importing a.py:


import a
# 1

Worked like a charm. You might be surprised. After all, the modules circularly import each other, and that should probably be a problem, right?

The thing is, simply having circular module imports isn't an issue in Python by itself. If a module's already been imported, Python's smart enough not to try to re-import it. However, depending on when each module tries to access functions or variables defined in the other, you might run into issues.

So, going back to our example, when we imported a.py, it didn't have any trouble importing b.py since b.py didn't need anything from a.py to be defined at the time of its import. The only reference in b.py to a is the call to a.f(). But that's within g(), and nothing in a.py or b.py calls g(). So everything's cool.

But what happens if we try importing b.py (without pre-importing a.py, that is):


import b
Traceback (most recent call last):
    File "<stdin>", line 1, in 
    File "b.py", line 1, in 
        import a
    File "a.py", line 6, in 
        print(f())
    File "a.py", line 4, in f
        return b.x
AttributeError: 'module' object has no attribute 'x'

The issue here is that during the import of b.py, it tries importing a.py, which then calls f(), which tries to access b.x. Yet b.x hasn't been defined yet. Hence the AttributeError.

At least one solution to this problem is pretty trivial. Just modify b.py to import a.py within g():


x = 1

def g():
    import a  # This will be evaluated only when g() is called
    print(a.f())

Now, when we import it, everything is fine:


import b
b.g()
# 1   Printed a first time since module 'a' calls 'print(f())' at the end
# 1   Printed a second time, this one is our call to 'g()'

9.2 Name Conflicts with Standard Python Library Module Names

One of the cool things about Python is all the modules that come "out of the box." But as a result, if you're not consciously aware, you can end up with your module name accidentally being the same as one from the standard library that ships with Python (like having a module named email.py that conflicts with the standard library module of the same name).

This can lead to serious issues. For example, if any module tries to import the standard library's version, but you've got a module with the same name in your project, it'll mistakenly import yours instead of the standard one.

So it's a good idea to be careful not to use the same names as modules in the Python standard library. It's way easier to change the name of a module in your project than to request a change in the standard library and wait for approval.

9.3 Visibility of Exceptions

Check out this file main.py:


import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)
        
def bad():
    e = None
    try:
        bar(int("1"))
    except KeyError as e:
        print('key error')
    except ValueError as e:
        print('value error')
    print(e)

bad()

Looks fine, the code should work, let's see what it'll output:


$ python main.py 1
Traceback (most recent call last):
    File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
        bad()
    File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
        print(e)
          ^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value

What just happened here? The problem is that in Python, an object in the exception block isn't accessible outside of it. (The reason is that otherwise, objects in that block would hang around in memory until garbage collection runs and removes references to them).

One way to avoid this issue is to store a reference to the exception block object outside of it so it remains accessible. Here's a version of the previous example using this technique, making the code work:


import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)
            
def good():
    exception = None
    try:
        bar(int("1"))    
    except KeyError as e:
        exception = e
        print('key error')
    except ValueError as e:
        exception = e
        print('value error')
    print(exception)
            
good()

9.4 Incorrect Use of the __del__ Method

When the interpreter deletes an object, it checks if the object has a __del__ function, and if it does, it calls it before removing the object. This is really handy if you want your object to clean up some external resources or cache.

Let's say you have a file mod.py like this:


import foo

class Bar(object):
        ...

def __del__(self):
    foo.cleanup(self.myhandle)

And you're trying to do this from another file another_mod.py:


import mod
mybar = mod.Bar()

And you get a nasty AttributeError.

Why? Because, as noted here, when the interpreter is shutting down, all global module variables are set to None. As a result, in the above example, by the time __del__ is called, the name foo is already set to None.

The solution to this "starred problem" is to use the special method atexit.register(). This way, when your program finishes executing (i.e., on normal exit), your handle's are cleared before the interpreter shuts down.

With that in mind, the fix for the above mod.py code could look like this:


import foo
import atexit
        
def cleanup(handle):
    foo.cleanup(handle)
        
class Bar(object):
    def __init__(self):
      ...

atexit.register(cleanup, self.myhandle)

This implementation provides a simple and reliable method to call any necessary cleanup after the program's normal termination. Obviously, the decision on how to handle the object associated with the name self.myhandle is left to foo.cleanup, but I think you get the idea.

2
Task
Python SELF EN, level 20, lesson 3
Locked
Intersection of names.
Intersection of names.
2
Task
Python SELF EN, level 20, lesson 3
Locked
Variable Scope.
Variable Scope.
1
Опрос
Iterators,  20 уровень,  3 лекция
недоступен
Iterators
Iterators
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION