المستورد السخري في بيثون
-
21-09-2019 - |
سؤال
أحاول هذا لمدة ساعتين تقريبًا الآن ، دون أي حظ.
لدي وحدة تبدو هكذا:
try:
from zope.component import queryUtility # and things like this
except ImportError:
# do some fallback operations <-- how to test this?
لاحقًا في الكود:
try:
queryUtility(foo)
except NameError:
# do some fallback actions <-- this one is easy with mocking
# zope.component.queryUtility to raise a NameError
أيه أفكار؟
تعديل:
لا يبدو أن اقتراح أليكس يعمل:
>>> import __builtin__
>>> realimport = __builtin__.__import__
>>> def fakeimport(name, *args, **kw):
... if name == 'zope.component':
... raise ImportError
... realimport(name, *args, **kw)
...
>>> __builtin__.__import__ = fakeimport
عند إجراء الاختبارات:
aatiis@aiur ~/work/ao.shorturl $ ./bin/test --coverage .
Running zope.testing.testrunner.layer.UnitTests tests:
Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds.
Error in test /home/aatiis/work/ao.shorturl/src/ao/shorturl/shorturl.txt
Traceback (most recent call last):
File "/usr/lib64/python2.5/unittest.py", line 260, in run
testMethod()
File "/usr/lib64/python2.5/doctest.py", line 2123, in runTest
test, out=new.write, clear_globs=False)
File "/usr/lib64/python2.5/doctest.py", line 1361, in run
return self.__run(test, compileflags, out)
File "/usr/lib64/python2.5/doctest.py", line 1282, in __run
exc_info)
File "/usr/lib64/python2.5/doctest.py", line 1148, in report_unexpected_exception
'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
File "/usr/lib64/python2.5/doctest.py", line 1163, in _failure_header
out.append(_indent(source))
File "/usr/lib64/python2.5/doctest.py", line 224, in _indent
return re.sub('(?m)^(?!$)', indent*' ', s)
File "/usr/lib64/python2.5/re.py", line 150, in sub
return _compile(pattern, 0).sub(repl, string, count)
File "/usr/lib64/python2.5/re.py", line 239, in _compile
p = sre_compile.compile(pattern, flags)
File "/usr/lib64/python2.5/sre_compile.py", line 507, in compile
p = sre_parse.parse(p, flags)
AttributeError: 'NoneType' object has no attribute 'parse'
Error in test BaseShortUrlHandler (ao.shorturl)
Traceback (most recent call last):
File "/usr/lib64/python2.5/unittest.py", line 260, in run
testMethod()
File "/usr/lib64/python2.5/doctest.py", line 2123, in runTest
test, out=new.write, clear_globs=False)
File "/usr/lib64/python2.5/doctest.py", line 1351, in run
self.debugger = _OutputRedirectingPdb(save_stdout)
File "/usr/lib64/python2.5/doctest.py", line 324, in __init__
pdb.Pdb.__init__(self, stdout=out)
File "/usr/lib64/python2.5/pdb.py", line 57, in __init__
cmd.Cmd.__init__(self, completekey, stdin, stdout)
File "/usr/lib64/python2.5/cmd.py", line 90, in __init__
import sys
File "<doctest shorturl.txt[10]>", line 4, in fakeimport
NameError: global name 'realimport' is not defined
ومع ذلك ، هو يفعل اعمل عندما أقوم بتشغيل نفس الرمز من وحدة التحكم التفاعلية Python.
المزيد من التحرير:
أنا استخدم zope.testing
وملف اختبار ، shorturl.txt
هذا له كل الاختبارات الخاصة لهذا الجزء من الوحدة النمطية الخاصة بي. أولا أقوم باستيراد الوحدة مع zope.component
متاح ، لإظهار واختبار الاستخدام المعتاد. غياب zope.*
تعتبر الحزم حالة حافة ، لذلك أنا أختبرها لاحقًا. وهكذا ، لا بد لي من reload()
الوحدة النمطية الخاصة بي بعد صنعها zope.*
غير متوفر ، بطريقة ما.
حتى الآن حاولت استخدام tempfile.mktempdir()
وفارغة zope/__init__.py
و zope/component/__init__.py
الملفات في tempdir ، ثم إدخال tempdir sys.path[0]
, وإزالة القديم zope.*
حزم من sys.modules
.
لم ينجح أيضًا.
أكثر تحريرًا:
في غضون ذلك ، جربت هذا:
>>> class NoZope(object):
... def find_module(self, fullname, path):
... if fullname.startswith('zope'):
... raise ImportError
...
>>> import sys
>>> sys.path.insert(0, NoZope())
ويعمل بشكل جيد على مساحة اسم جناح الاختبار (= لجميع الواردات في shorturl.txt
) ، لكنها لم يتم تنفيذها في الوحدة الرئيسية الخاصة بي ، ao.shorturl
. ولا حتى عندما أنا reload()
هو - هي. أي فكرة لماذا؟
>>> import zope # ok, this raises an ImportError
>>> reload(ao.shorturl) <module ...>
الاستيراد zope.interfaces
يرفع ImportError
, ، لذلك لا يصل إلى الجزء الذي أستورد فيه zope.component
, ، و يبقى في مساحة اسم AO.Shorturl. لماذا؟!
>>> ao.shorturl.zope.component # why?!
<module ...>
المحلول
فقط monkeypatch في builtins
نسختك الخاصة من __import__
- يمكن أن يرفع ما تريده عندما يدرك أنه يتم استدعاؤه على الوحدات النمطية المحددة التي تريد أن تسخر من الأخطاء. يرى المستندات للحصول على تفاصيل وفيرة. بقسوة:
try:
import builtins
except ImportError:
import __builtin__ as builtins
realimport = builtins.__import__
def myimport(name, globals, locals, fromlist, level):
if ...:
raise ImportError
return realimport(name, globals, locals, fromlist, level)
builtins.__import__ = myimport
بدلا من ...
, ، يمكنك الترميز المتشددين name == 'zope.component'
, ، أو ترتيب الأشياء بشكل أكثر مرونة مع رد اتصال خاص بك يمكن أن تجعل الواردات ترتفع عند الطلب في حالات مختلفة ، اعتمادًا على احتياجات الاختبار المحددة ، دون مطالمك بترميز متعددة __import__
-وظائف متشابهة ؛-).
لاحظ أيضًا أنه إذا كان ما تستخدمه بدلاً من import zope.component
أو from zope.component import something
, ، هو from zope import component
, ، ال name
ثم سيكون 'zope'
, ، و 'component'
سيكون العنصر الوحيد في fromlist
.
تعديل: مستندات __import__
الوظيفة قل أن الاسم للاستيراد builtin
(كما في بيثون 3) ، ولكن في الواقع تحتاج __builtins__
- لقد قمت بتحرير الكود أعلاه بحيث يعمل في كلتا الحالتين.
نصائح أخرى
هذا هو ما أدت إلى تقديري.
يستخدم PEP-302 "خطافات استيراد جديدة". (تحذير: وثيقة PEP-302 وملاحظات الإصدار الأكثر إيجازًا التي ربطتها ليست بالضبط دقيق.)
أنا أستعمل meta_path
لأنه في أقرب وقت ممكن في تسلسل الاستيراد.
إذا تم بالفعل استيراد الوحدة النمطية (كما في حالتي ، لأنه في وقت سابق يسخر من الاختبارات ضدها) ، فمن الضروري إزالتها من sys.modules قبل القيام reload
على الوحدة التابعة.
Ensure we fallback to using ~/.pif if XDG doesn't exist.
>>> import sys
>>> class _():
... def __init__(self, modules):
... self.modules = modules
...
... def find_module(self, fullname, path=None):
... if fullname in self.modules:
... raise ImportError('Debug import failure for %s' % fullname)
>>> fail_loader = _(['xdg.BaseDirectory'])
>>> sys.meta_path.append(fail_loader)
>>> del sys.modules['xdg.BaseDirectory']
>>> reload(pif.index) #doctest: +ELLIPSIS
<module 'pif.index' from '...'>
>>> pif.index.CONFIG_DIR == os.path.expanduser('~/.pif')
True
>>> sys.meta_path.remove(fail_loader)
حيث يشبه الرمز داخل pif.index:
try:
import xdg.BaseDirectory
CONFIG_DIR = os.path.join(xdg.BaseDirectory.xdg_data_home, 'pif')
except ImportError:
CONFIG_DIR = os.path.expanduser('~/.pif')
للإجابة على السؤال حول سبب وجود وحدة تم إعادة تحميلها حديثًا خصائص للأحمال القديمة والجديدة ، إليك ملفان مثال.
الأول هو وحدة نمطية y
مع حالة فشل الاستيراد.
# y.py
try:
import sys
_loaded_with = 'sys'
except ImportError:
import os
_loaded_with = 'os'
والثاني هو x
مما يوضح كيف يمكن أن تؤثر ترك المقابض للوحدة النمطية على خصائصها عند إعادة تحميلها.
# x.py
import sys
import y
assert y._loaded_with == 'sys'
assert y.sys
class _():
def __init__(self, modules):
self.modules = modules
def find_module(self, fullname, path=None):
if fullname in self.modules:
raise ImportError('Debug import failure for %s' % fullname)
# Importing sys will not raise an ImportError.
fail_loader = _(['sys'])
sys.meta_path.append(fail_loader)
# Demonstrate that reloading doesn't work if the module is already in the
# cache.
reload(y)
assert y._loaded_with == 'sys'
assert y.sys
# Now we remove sys from the modules cache, and try again.
del sys.modules['sys']
reload(y)
assert y._loaded_with == 'os'
assert y.sys
assert y.os
# Now we remove the handles to the old y so it can get garbage-collected.
del sys.modules['y']
del y
import y
assert y._loaded_with == 'os'
try:
assert y.sys
except AttributeError:
pass
assert y.os
إذا كنت لا تمانع في تغيير البرنامج نفسه ، فيمكنك أيضًا وضع مكالمة الاستيراد في وظيفة وتصحيح في الاختبارات الخاصة بك.