I have a problem overriding the method where from...import statement is used. Some example to illustrate the problem:
# a.py module
def print_message(msg):
print(msg)
# b.py module
from a import print_message
def execute():
print_message("Hello")
# c.py module which will be executed
import b
b.execute()
I'd like to override print_message(msg) method without changing code in a or b module. I tried in many ways but from...import imports the original method. When I changed the code to
import a
a.print_message
then I see my change.
Could you suggest how to solve this problem?
------------------ Update ------------------
I tried to do that like below e.g.:
# c.py module
import b
import a
import sys
def new_print_message(msg):
print("New content")
module = sys.modules["a"]
module.print_message = new_print_message
sys.module["a"] = module
But this is not working where I'm using for...import statement. Is working only for import a but as I wrote I don't want change code in b.py and a.py modules.
With your a and b modules untouched you could try implementing c as follows:
import a
def _new_print_message(message):
print "NEW:", message
a.print_message = _new_print_message
import b
b.execute()
You have to first import a, then override the function and then import b so that it would use the a module that is already imported (and changed).
module1.py
def function1():
print("module1 function1")
function2()
def function2():
print("module1 function2")
module2.py
import module1
test = module1.function1()
print(test)
""" output
module1 function1
module1 function2
"""
def myfunction():
print("module2 myfunction")
module1.function2 = lambda: myfunction()
test = module1.function1()
print(test)
"""output
module1 function1
module2 myfunction
"""
Related
Given the following files:
a.py
-----
class CommonClass(object):
def do_thing(self):
pass
b.py
-----
from a import CommonClass
class SubClassA(CommonClass):
def do_thing(self):
print("I am A")
class SubClassB(CommonClass):
def do_thing(self):
print("I am B")
c.py
-----
from a import CommonClass
from b import SubClassA
if __name__ == "__main__":
for member in CommonClass.__subclasses__():
member().do_thing()
I would expect only SubClassA is imported, and visible when looping through subclasses of CommonClass, but it seems SubClassB is imported as well.
I am running Python 3.8.5 and this is the output of python3 c.py:
$ python3 c.py
I am A
I am B
How can I only import the classes I want?
You did import only SubClassA to c.py. This can be tested by doing
x = SubClassB()
or
x = b.SubClassB()
Both will result in a NameError.
The thing is, that when you import a file, it is actually being ran, even when using from x import y!
This can be easily seen by adding a print("I'm from b.py") at the end of b.py and then running c.py.
This makes both SubClassA and SubClassB be subclasses of CommonClass, which you imported. So, while you don't have access to the SubClassB name, it is still a subclass of CommonClass and you can access it from there.
In general you don't have to import a module to be able to use its objects. Importing expands your namespace to include that module so you can create an object directly. You can still use this module's objects even without importing it if you acquired them in some other way (like importing a third module which returns objects from the second one).
Anyway right now, you are not really using even the imported SubClassA. If you want to "allow" certain classes to be considered only from an external source, you can create an allowed set of classes:
from a import CommonClass
from b import SubClassA
allowed_classes = {SubClassA}
if __name__ == "__main__":
for member in CommonClass.__subclasses__():
if member in allowed_classes:
member().do_thing()
Which only prints I am A
from a import CommonClass
from b import SubClassA
if __name__ == "__main__":
h = CommonClass.__subclasses__()[0]()
h.do_thing()
You can do this.
I need to test a function in a module that import another module which raises an exception when imported.
#a.py
raise ValueError("hello")
my_const = 'SOMETHING'
#b.py
from a import my_const
def foo():
# do something with my_const
return "expected_result"
#test_foo.py
def test_foo():
from b import foo
assert foo() == "expected_result"
Here when I import foo in test_foo.py, a.py get imported in b.py, an exception is raised and the import is never completed so my_const is not available in b.py.
I'm not allowed to modify neither a.py or b.py. Also, using unittest.patch and #patch('a', 'my_const') does import a.py so it doens't work.
It is possible create the module dynamically with the import lib and add it to sys.modules, but is there another solution that doesn't require importlib ?
As far as I know, you can create and importe the module dynamically. Here is a code inspired from the
"Approximating importlib.import_module()" section in the import lib documentation
from importlib.util import module_from_spec, find_spec
import sys
def patched_import(name, **kwargs):
spec = find_spec(name)
m = module_from_spec(spec)
for k in kwargs:
setattr(m, k, kwargs[k])
sys.modules[name] = m
Edit: My solution should be ok for a mock-up but be careful as manipulation of referential can have side effects.
To use it, just do:
patched_import('a', my_const='stuff')
Before importing b.py.
I have a module I need to test that calls a function on import but I cannot call this function for various reasons. So I am mocking this function but even mocking it calls import.
For example I am testing mod1.py that looks like this:
import os
def bar():
return 'foo'
def dont_call():
os.listdir("C:\\tmp")
dont_call()
And my test looks something like this:
import mock
#mock.patch("mod1.dont_call")
def test_mod1(mock_dont_call):
import mod1
assert mod1.bar()=='foo'
if __name__=="__main__":
test_mod1()
The problem is os.listdir is called.
I cannot change mod1 so what can I do?
I am using python2.7.
To put this in context I am testing a module that opens a database connection on import which I do not agree with but I can see the reasoning behind it. Unfortunately I cannot access this database on my QA machine.
If you want code to 'not' be executed on import put them inside the following condition:
In mod1.py, do the following:
if __name__=="__main__":
dont_call()
This is because, by default when you import a python module, all the code in it gets executed. By adding the above condition, you are explicitly stating that dont_call() is to be called only when the file it run as a script and not when it is imported in other modules.
The workaround I found was to mock what dont_call was calling giving me something like this:
import mock
#mock.patch("os.listdir")
def test_mod1(mock_dont_call):
import mod1
assert mod1.bar()=='foo'
if __name__=="__main__":
test_mod1()
Check your dir
$tree.
test_shot/
├── mod1.py
├── __pycache__
│ └── mod1.cpython-310.pyc
└── test.py
Below code works fine for me.
mod1.py
import os
def bar():
return 'foo'
def dont_call():
os.listdir(".")
def call_this():
print('called this')
call_this()
dont_call()
test.py
import mock
#mock.patch("mod1.dont_call")
def test_mod1(mock_dont_call):
import mod1
assert mod1.bar()=='foo'
if __name__=="__main__":
test_mod1()
Here is output:
$cd test_shot
$python3 test.py
called this
I have 3 files a.py, b.py, c.py
I am trying to dynamically import a class called "C" defined in c.py from within a.py
and have the evaluated name available in b.py
python a.py is currently catching the NameError. I'm trying to avoid this and create an
instance in b.py which calls C.do_int(10)
a.py
import b
#older
#services = __import__('services')
#interface = eval('services.MyRestInterface')
# python2.7
import importlib
module = importlib.import_module('c')
interface = eval('module.C')
# will work
i = interface()
print i.do_int(10)
# interface isn't defined in b.py after call to eval
try:
print b.call_eval('interface')
except NameError:
print "b.call_eval('interface'): interface is not defined in b.py"
b.py
def call_eval(name):
interface = eval(name)
i = interface()
return i.do_int(10)
c.py
class C(object):
my_int = 32
def do_int(self, number):
self.my_int += number
return self.my_int
How can I achieve this?
interface only exists in a's namespace. You can put a reference to the interface into b's namespace like this
b.interface = interface
try:
print b.call_eval('interface')
except NameError:
print "b.call_eval('interface'): interface is not defined in b.py"
I'm not sure why you're not just passing the interface to call_eval though
I'm sure there should be a better solution by totally avoiding this.
But this could do the trick:
a.py:
shared_variables = {}
import b
import c
shared_variables['C'] = c.C
b.do_something_with('C')
b.py:
from __main__ import shared_variables
def do_something_with(name):
print(shared_variables[name])
If a.py already loads the class, I fail to see the reason to pass it by name. Instead, do
# b.py
def call_eval(klass):
j = klass()
return i.do_int(10)
and, in a.py, do
import importlib
module = importlib.import_module('c')
interface = getattr(module, 'C')
b.call_eval(interface)
I have the following files in my directory:
foo/
foo.py
foolib/
__init__.py
bar.py
Within __init__.py:
__all__ = ["bar"]
Within bar.py:
class Bar:
def __init__(self):
None
def hello(self):
print("Hello World")
return
def hi():
print("Hi World")
Now if I have the following code within foo.py:
from foolib import *
bar.hi()
foobar = Bar()
foobar.hello()
"Hi World" prints, but I get a NameError for Bar(). If I explicitly import the module:
from foolib.bar import *
I get the expected output "Hello World".
Is there a way for me to import classes from the modules, without explicitly calling them? I feel like I am missing something in the __init__ file. Either that or I am flagrantly violating some Python best practice.
To import the class you must import the class, somewhere. When you do from foolib import *, because of your __init__.py this imports the module bar. It doesn't allow you to access anything inside that module.
If you want to automatically access everything in bar from the foolib package without having to import bar, you could put this in __init__.py:
from bar import *
This makes everything in bar available directly in foolib.