Download - Import this, that, and the other thing: custom importers

Transcript
Page 1: Import this, that, and the other thing: custom importers

If you do not know what __path__ is,

this talk is NOT for you.

Sorry.

Page 2: Import this, that, and the other thing: custom importers

import this, that, and the other thingCustom importers in Python

Brett Cannonwww.DrBrett.ca

[email protected]

Slides are sparse, so do listen to what I say.

Page 3: Import this, that, and the other thing: custom importers

Thanks ...

• Python Software Foundation• PyCon Financial Aid committee

• Nasuni• Jesse Noller

Page 4: Import this, that, and the other thing: custom importers

What the heck is an importer?

Relevant since Python 2.3

Page 5: Import this, that, and the other thing: custom importers

importer=

finder + loader

Page 6: Import this, that, and the other thing: custom importers

A finder finds modules.

Page 7: Import this, that, and the other thing: custom importers

A loader loads modules.

Page 8: Import this, that, and the other thing: custom importers

“Why do I want one?”

Customization/control, easier to work w/ than __import__

Page 9: Import this, that, and the other thing: custom importers

How are custom importers used by

import?

Simplified view; ignoring implicit importers

Page 10: Import this, that, and the other thing: custom importers

Meta pathsys.meta_path

Page 11: Import this, that, and the other thing: custom importers

Start

for finder in sys.meta_path:

loader = finder.find_module(name, path)

return loader.load_module(name)

...

True

False

What ‘path’ arg is

Page 12: Import this, that, and the other thing: custom importers

Pathsys.path or __path__, sys.path_hooks,

& sys.path_importer_cache

Page 13: Import this, that, and the other thing: custom importers

... Parent module

has __path__

search = parent's __path__

search = sys.path

search

True

False

Page 14: Import this, that, and the other thing: custom importers

for entry in search:

finder = sys.path_importer_cache[entry]

loader = finder.find_module(name)

return loader.load_module(name)

Search

path hookFalse

True

False finder

raise ImportError

True

Page 15: Import this, that, and the other thing: custom importers

for hook in sys.path_hooks:

finder = hook(entry)

sys.path_importer_cache[entry] = finder

sys.path_importer_cache[entry] = dummy

path hook

finder

False

True

True/False = ImportError (not) raised

Page 16: Import this, that, and the other thing: custom importers

how do I write my own

importer?Only masochists need apply.

Page 17: Import this, that, and the other thing: custom importers

Option 1:Painfully from

scratchRead PEP 302 for the gory details.

Page 18: Import this, that, and the other thing: custom importers

Option 2:Use importlib

Available since Python 3.1.I have suffered so you don’t have to.

Page 19: Import this, that, and the other thing: custom importers

Option 3: importers

http://packages.python.org/importers/

File path abstraction on top of importlib.Treating as purgatory for importlib

inclusion.

If a lesson here, then it is to use option 2 or 3 depending on your needs.Rest of talk is about lessons that led to ‘importers’.

Page 20: Import this, that, and the other thing: custom importers

Using azipfile importeras an example

Assuming use of importlib.Talking from perspective of using an archive.

Page 21: Import this, that, and the other thing: custom importers

we need a hookFor sys.path_hooks.

Page 22: Import this, that, and the other thing: custom importers

Refresher:Hooks look for a finder for a path

Path either from sys.path or __path__

Page 23: Import this, that, and the other thing: custom importers

Hooks can get funky paths

E.g. /path/to/file/code.zip/some/pkg

Search backwards looking for a file; find a directory then you have gone too far.

Page 24: Import this, that, and the other thing: custom importers

Consider caching archive file objects

No need to keep 3 connection objects open for the same sqlite3 file

Page 25: Import this, that, and the other thing: custom importers

Pass your finder the “location”:

1)the path/object &2) the package path

Import assumes you are looking in a part of a package.

Page 26: Import this, that, and the other thing: custom importers

Raise ImportError if you got nuthin’

Page 27: Import this, that, and the other thing: custom importers

Have finder, will look for code

Page 28: Import this, that, and the other thing: custom importers

Don’t treat modules as code but as files

Just trust me. Too many people/code make this assumption already for stuff like __file__, __path__, etc.

Page 29: Import this, that, and the other thing: custom importers

You did remember where in the

package you are looking, RIGHT?!?

Needed because of __path__ manipulation by user code.

Page 30: Import this, that, and the other thing: custom importers

fullname.rpartition(‘.’)[-1]

Page 31: Import this, that, and the other thing: custom importers

Need to care about packages &

modulessome/pkg/name/__init__.py

andsome/pkg/name.py

Care about bytecode if you want.Notice how many stat calls this takes?

Page 32: Import this, that, and the other thing: custom importers

Avoid caching within a finder

Blame sys.path_importer_cache

Page 33: Import this, that, and the other thing: custom importers

Tell the loader if package &path to code

Don’t Repeat Yourself ... within reason.

Page 34: Import this, that, and the other thing: custom importers

Nuthin’?Give back None

Page 35: Import this, that, and the other thing: custom importers

Now it gets tricky

Writing a loader.

Page 36: Import this, that, and the other thing: custom importers

Are you still thinking in terms of

file paths?

Page 37: Import this, that, and the other thing: custom importers

importlib.abc.PyLoader

• source_path()• Might be changing...

• is_package()• get_data()

Everything in terms of exactly what it takes to import source

Page 38: Import this, that, and the other thing: custom importers

importlib.abc.PyPycLoader

• source_path()• is_package()• get_data()• source_mtime()• bytecode_path()

• Might be changing...

This is what is needed to get source w/ bytecode right

Page 39: Import this, that, and the other thing: custom importers

Reasons to ignore .pyc

• Jython, IronPython couldn’t care less.• Safe to support, though.

• Another thing to code up.• Bytecode is just an optimization.• If you only ship .pyc for code

protection, stop it.

Page 40: Import this, that, and the other thing: custom importers

What to do when using

importlib ABCs

Page 41: Import this, that, and the other thing: custom importers

Require anchor point for paths

somewhere/mod.py is too ambiguous

Too hazy as to where a relative path is anchored; archive? Package location?

Page 42: Import this, that, and the other thing: custom importers

Consider cachingstat calls

Only for stand-alone loaders!Also consider caching if package or not.

Consider whether storage is read-only, append-only, or read-write.

Page 43: Import this, that, and the other thing: custom importers

Don’t overdo error checking

EAFP is your friend.

Page 44: Import this, that, and the other thing: custom importers

Perk of importers is the abstraction

Page 45: Import this, that, and the other thing: custom importers

Lazy loader mix-inwritten in19 lines

Page 46: Import this, that, and the other thing: custom importers

class Module(types.ModuleType): pass

class Mixin: def load_module(self, name): if name in sys.modules: return super().load_module(name) # Create a lazy module that will type check. module = LazyModule(name) # Set the loader on the module as ModuleType will not. module.__loader__ = self # Insert the module into sys.modules. sys.modules[name] = module return module

class LazyModule(types.ModuleType): def __getattribute__(self, attr): # Remove this __getattribute__ method by re-assigning. self.__class__ = Module # Fetch the real loader. self.__loader__ = super(Mixin, self.__loader__) # Actually load the module. self.__loader__.load_module(self.__name__) # Return the requested attribute. return getattr(self, attr)

Page 47: Import this, that, and the other thing: custom importers

... or you could use the importers package

http://packages.python.org/importers/

Page 48: Import this, that, and the other thing: custom importers

Fin