09. Модули

09. Модули

09. Модули

6 април 2016

Модули 101

.pyc

import

В този ред на мисли, показвали ли сме ви това:

import __hello__

Модули

from module import a, b, c
a()

Модули

from module import a, b, c
a()

from module import a as b
b()

Модули

from module import a, b, c
a()

from module import a as b
b()

from django.contrib.auth.models import User
my_user = User()

Модули

from django.db import models
my_model = models.Model()

Модули

import datetime
now = datetime.datetime.now()

if __name__ == "__main__":

Така можете да сте сигурни, че текущият файл е изпълнен, а не импортнат

Search Path

>>> import sys
>>> sys.path
[
'',
'/usr/lib/python3.4',
'/usr/lib/python3.4/plat-linux',
'/usr/lib/python3.4/lib-dynload',
'/usr/lib/python3.4/site-packages',
]

dir()

Връща списък със всички имена в модул

import sys
dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__',
'__loader__', '__name__', '__package__', '__spec__', '__stderr__',
'__stdin__', '__stdout__', '_clear_type_cache', '_current_frames',
'_debugmallocstats', '_getframe', '_home', '_mercurial', '_xoptions',
'abiflags', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix',
'builtin_module_names', 'byteorder', 'call_tracing', 'callstats',
'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
'float_repr_style', 'getallocatedblocks', 'getcheckinterval',
'getdefaultencoding', 'getdlopenflags', 'getfilesystemencoding',
'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof',
'getswitchinterval', 'gettrace', 'hash_info', 'hexversion',
'implementation', 'int_info', 'intern', 'maxsize', 'maxunicode',
'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache',
'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace',
'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info',
'warnoptions']

Пакети

panda/
    __init__.py
    head.py
    body.py
    belly.py
    eyes.py
    tail.py
    # ...

Импортване на всичко от даден модул

from panda import *

__all__

Релативни пътища

Можем да пропуснем търсенето в sys.path и директно да импортнем нещо от текущата директория

from . import panda

Можем да търсим и в "горния" възел

from .. import panda

Или:

from ..panda import tail

Инсталиране на пакети

Long story short

pip install package-name-goes-here

В живия живот

Драми

virtualenv

Изолирани обкръжения за python кода ни, в които можем да инсталираме каквото си искаме и да ползваме която си искаме версия на python

$ virtualenv ~/panda_env
$ source ~/panda_env/bin/activate
(panda_env)$ which python
/home/pandyo/panda_env/bin/python
(panda_env)$ which pip
/home/pandyo/panda_env/bin/pip
(panda_env)$ pip install dateutils

virtualenv

>>> import dateutils
>>> dateutils
<module 'dateutils' from '/home/pandyo/panda_env/lib/python3.4/site-packages/dateutils/__init__.py'>

Когато приключим можем да изпълним shell функцията deactivate или просто да излезем от shell-а

virtualenv is awesome!

Изолация, разделяне на отговорности, поддържане на адекватни версии на зависимостите, …

Когато нещо много страшно се счупи триете една папка и инсталирате само пакетите, които са нужни за един проект

virtualenv is (pretty) awesome!

Само т'ва експлицитно писане на директории и source-ване на shell скриптове е малко грозно…

virtualenvwrapper is also awesome!

$ sudo pip install virtualenvwrapper

$ mkvirtualenv panda
$ workon panda
(panda)$

От къде ги тегли?

PyPI - the Python Package Index

Как да качим пакет там?

Пример

from setuptools import setup, find_packages

setup(
    name="hello_world",
    version="0.1",
    packages=find_packages(),
    install_requires=['world>=1'],
    author="Me",
    author_email="me@example.com",
    description="This is an Example Package",
    license="PSF",
    keywords="hello world example examples",
    url="http://example.com/hello_world/"
)

Classifiers

setup(...,
    classifiers=[
      'Development Status :: 4 - Beta',
      'Environment :: Console',
      'Environment :: Web Environment',
      'Intended Audience :: End Users/Desktop',
      'Intended Audience :: Developers',
      'Intended Audience :: System Administrators',
      'License :: OSI Approved :: Python Software Foundation License',
      'Operating System :: MacOS :: MacOS X',
      'Operating System :: Microsoft :: Windows',
      'Operating System :: POSIX',
      'Programming Language :: Python',
      'Topic :: Communications :: Email',
      'Topic :: Office/Business',
      'Topic :: Software Development :: Bug Tracking',
    ],
)

List trove classifiers

Добре, имаме го и сега какво?

PyPI

python setup.py register
python setup.py sdist upload

Терминология

wheel

PEP 427

Защо пък wheel?

Какво да направим за целта?

pip install wheel
python setup.py bdist_wheel

AST

A tree representation of the abstract syntactic structure of source code written in a programming language.

Бърз пример

Имаме следната тривиална функция:

def sum(a, b):
    return a + b

Можем да изпарсим функцията и да построим AST дърво за нея:

>>> ast.parse(inspect.getsource(sum))
<<< <_ast.Module at 0x7f3e85d78e48>

>>> sum_ast = ast.parse(inspect.getsource(sum))

>>> ast.dump(sum_ast)
Module(body=[
    FunctionDef(name='sum', args=arguments(args=[
        arg(arg='a', annotation=None),
        arg(arg='b', annotation=None),
      ], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[
        Return(value=BinOp(left=Name(id='a', ctx=Load()), op=Add(), right=Name(id='b', ctx=Load()))),
      ], decorator_list=[], returns=None),
  ])

... очевидно.

Визуализация

По-сложна визуализация на

def fib(n):
   if n <= 1:
       return n
   else:
       return fib(n-1) + fib(n-2)

Meet the nodes

Всеки елемент от питонски код се представя като AST обект

Литерали

Променливи

Meet the nodes

Всеки елемент от питонски код се представя като AST обект

Изрази

Meet the nodes

Import

Control flow

Meet the nodes

Функции и класове

...

И какво от това?

Можем да се набутаме в парсването на даден модул и да правим магарии.

ast.NodeVisitor сканира дадено ast дърво. Можем да го наследим.

ast.NodeVisitor.visit_{нещата изброени в предните слайодве} ще бъдат изпълнени по време на парсване.

Какви функции са дефинирани в модул

>>> class FuncLister(ast.NodeVisitor):
        def visit_FunctionDef(self, node):
            print(node.name)
            self.generic_visit(node)

>>> import tictac
>>> tictac_parsed = ast.parse(inspect.getsource(tictac))
>>> FuncLister().visit(tictac_parsed)
__init__
state
_convert_to_coords
__setitem__
__getitem__

И по-забавното

Напълно нормално е, че 1+3 връща 4 и типът на резултата е int:

>>> tree = ast.parse("print(1+3)\nprint(type(1+3))")

>>> exec(compile(tree, "<ast>", "exec"))
4
<class 'int'>

И по-забавното

Обаче... ast.NodeTransformer :)

>>> class IntegerWrapper(ast.NodeTransformer):
        """Transform all integers to string."""
        def visit_Num(self, node):
            if isinstance(node.n, int):
                return ast.Call(func=ast.Name(id='str', ctx=ast.Load()),
                                args=[node], keywords=[])
            return node

>>> tree = IntegerWrapper().visit(ast.parse("print(1+3)\nprint(type(1+3))"))

>>> ast.fix_missing_locations(tree)

>>> exec(compile(tree, "<ast>", "exec"))
13
<class 'str'>

Не е ли твърде късно след импорт?

Да.

>>> with open('tictac.py', 'r') as f:
        tictac_module = f.read()

>>> ast.parse(tictac_module)
...

Въпроси?