Python namespace in one file

Organizing your code: 3 ways to create namespaces in Python

You’ve written your script and then you noticed that it is a single file with 30 functions, 5 global variables, some main instructions. This is difficult to skim and, in consequence, difficult to read and edit in the future. You should separate all your code into sections: first you can try and make some big comments, but the code may still look dense after that; this means it is time to create some namespaces. A namespace is an environment in which symbols have a unique meaning. In Python and many other programming languages in the C tradition, one function makes a namespace (for most uses, also called a «scope»): the variables declared in its body won’t be accessible outside of it, unless you explicitly allow that — which is considered a bad style. There are other ways to create a namespace, and making use of that is a great idea to organize your code; and it is not me saying that, it is Tim Peters:

Sparse is better than dense.
(. )
Namespaces are one honking great idea — let’s do more of those!
The Zen of Python, by Tim Peters

Let’s see some ways to create namespaces. Aiming for modularity is a good practice in any programming languages; the following examples are in Python, but I can see them being applied very similarly in JavaScript and other languages too.

Читайте также:  Понятие тегов языка html

The file system way: modules

This is what people usually think about first when planning on creating namespaces. If you import any module (except when using from . import * , which is not recommended most of the times), the objects exported by that module will be available in dot notation:

import itertools for letter in itertools.cycle('ABCD'): . cycle('ABCD') # NameError: name 'cycle' is not defined 

In the code snippet, the itertools module was imported and is only accessed with the itertools symbol. You could make your own function called cycle , and that would be different from itertools.cycle . The most important, though, is that you can split your project into different files and organize code like this:

def get_movie_posters(): . def download_poster(): . def parse_poster_response(): . def get_movie_ratings(): . movie_posters = get_movie_posters() movie_ratings = get_movie_ratings() def analyze_movies(posters, ratings): . analyze_movies(movie_posters, movie_ratings) 
import posters import ratings import analyzer analyzer.run(posters.get(), ratings.get()) 

For that, you need to distribute the code into different files. Assuming the snippet above is in a main.py file you want to run, your folder structure could be:

project/ main.py analyzer.py ratings.py posters.py 

It is necessary that the modules you create do not have the same name as a module in the standard library. It is also possible to create subfolders, as long as all of them have a __init__.py (it can just be an empty file with that name), as well as execute the project folder as a whole (we would call that a «package»). For more details, check the documentation. It may happen, however, that you are not free to create extra files. Despite that, it is still possible to have multiple namespaces in a single script by using the solutions below. The code may not become shorter, but it would at least be more organized.

Читайте также:  Css transform rotate background

The OOP-inspired way: classes

from abc import ABC from typing import final @final class PostersNamespace(ABC): @staticmethod def get(): . @staticmethod def _download_poster(): . @staticmethod def _parse_poster_response(): . 

You can have objects shared between the functions in the class, but not necessarily with the rest of the code:

from abc import ABC from typing import final @final class PostersNamespace(ABC): url = 'https://www.postersite.com' @classmethod def get(cls): return cls.url PostersNamespace.get() # https://www.postersite.com PostersNamespace.url # https://www.postersite.com url # NameError: name 'url' is not defined 

I call this «OOP-inspired» (and not just «OOP») because we are using classes only for their namespace-like behavior in Python — they are not being used as a template for an object, so they should not be instantiated (that’s why we are inheriting from ABC in the snippets above) or be used as a parent class (that’s why we are annotating the classes as @final 1 ). You could achieve a similar effect with dictionaries, but it is not as easy/organized to insert functions with multiple statements into them:

postersNamespace =  "get": lambda: . # You either rely on lambda functions only. "download_poster": download_poster # . or use a function defined elsewhere, # but then the namespace won't be very delimited visually > 

Of course, you can also go full OOP and make those classes more than mere namespaces and actually initialize objects out of them:

class Poster: url = 'https://www.postersite.com' @staticmethod def __init__(self, movie): . movies = [. ] posters = [Posters(movie) for movie in movies] 

This, however, requires you to manage the state of more objects — and take care so that the code doesn’t become spaghetti again.

The functional way: closures

In Python, you can create functions inside of functions. Make use of that to separate sections in your code:

def posters(): url = 'https://www.postersite.com' def _download_poster(): . def _parse_poster_response(): . . return posters 

Differently from the OOP-style solution, this limits the access you have to the different objects in the namespace, as you’re restricted to the returned object. One work-around is to make functions return other functions (which will still have access to the original scope):

def make_get_poster_function(): url = 'https://www.postersite.com' def _download_poster(): . def _parse_poster_response(): . def get_poster(movie): r = requests.get(url) . return get_poster 

In the snippet above, the function returned by make_get_poster_function() is able to be executed successfully, as the reference to «url» inside of it goes along with it when it is returned.

Conclusion

  1. The @final decorator does not prevent inheritance during runtime, only during static type checking (when using Mypy, for example). Python does not provide a way to totally prevent a class from inheriting from another one; you can only simulate that with custom dunder methods (see here and here). ↩

Источник

Объединение нескольких пакетов в одно пространство имен Python

Иногда возникает необходимость разделить несколько пакетов, лежащих в одном пространстве имен по разным физическим путям. Например, если вы хотите иметь возможность передавать разную компоновку плагинов, имея возможность в последствии добавлять их, не контролируя их расположение, и, при этом, обращаться к ним через один namespace.

Эта шпаргалка, которая подойдет скорее для новичков, посвящена пространствам имен Python.

Давайте рассмотрим, как это можно сделать в разных версиях Python, так как хотя Python2 и перестает скоро поддерживаться, многие из нас как раз сейчас меж двух огней, и это как раз один из важных нюансов при переходе.

image

Рассмотрим такой пример:

Мы хотим получить структуру пакетов:

namespace1 package1 module1 package2 module2

При этом пакеты распределены в такой структуре папок:

 path1 namespace1 package1 module1 path2 namespace1 package2 module2 

Допустим, что так или иначе path1 и path2 уже добавлены в sys.path. Нам надо получить доступ к module1 и module2:

 from namespace1.package1 import module1 from namespace1.package2 import module2 

Что произойдет в Python 3.7 при выполнении этого кода? Все работает чудесно:

С PEP-420 в Python 3.3, появилась поддержка неявных пространств имен. Кроме того при импорте пакета с версии py33 не надо создавать файлы __init__.py. А при импорте namespace, это просто _запрещено_. Если в одной или обоих директориях и именем namespace1 будет присутствовать файл __init__.py, произойдет ошибка на импорте второго пакета.

ModuleNotFoundError: No module named 'namespace1.package2' 

Таким образом наличие инишника явно определяет пакет, а пакеты объединять нельзя, это единая сущность. Если вы начинаете новый, независящий от старых разработок, проект и пакеты будут устанавливаться с помощью pip, то придерживаться надо именно такого способа. Однако иногда нам в наследство достается старый код, который тоже надо поддерживать, по-крайней мере некоторое время, или переносить на новую версию.

Перейдем к Python 2.7. С этой версией уже интереснее, нужно сначала добавлять __init__.py в каждую директорию для создания пакетов, иначе интерпретатор просто не распознает в этом наборе файлов пакет. А затем прописать в __init__ файлах относящихся к namespace1 явное объявление пространства имен, в противном случае, произойдет импорт только первого пакета.

from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

Что при этом происходит? Когда интерпретатор доходит до первого импорта, выполняется поиск в sys.path пакета с таким именем, он находится в path1/namespace1 и интерпретатор выполняет path1/namespace1/__init__.py. Далее поиск не ведется. Однако функция extend_path сама выполняет поиск уже по всему sys.path, находит все пакеты с именем namespace1 и инишником и добавляет их в переменную __path__ пакета namespace1, которая используется для поиска дочерних пакетов в этом пространстве имен.

В официальных гайдах рекомендуется, чтобы инишники были одинаковыми при каждом размещении namespace1. На самом деле, они могут быть пустыми все, кроме первого, который находится при поиске в sys.path, в котором должен быть вызов pkgutil.extend_path, потому что остальные не выполняются. Однако, конечно, лучше чтобы действительно вызов был в каждом инишнике, чтобы не завязывать свою логику «на случай» и не гадать какой инишник выполнился первым, ведь порядок поиска может измениться. По этой же причине не стоит располагать никакую другую логику __init__ файлах области переменных.

Это сработает и в последующих версиях и этот код можно использовать для написания совместимого кода, но нужно учитывать, что выбранного способа надо придерживаться в каждом распространяемом пакете. Если на 3-й версии в некоторые пакеты положить инишник в вызовом pkgutil.extend_path, а некоторые оставить без инишника, это не сработает.
Кроме того этот вариант подходит и для случая, когда вы планируете устанавливать с помощью python setup.py install.

Еще один способ, который сейчас считается несколько устаревшим, но его еще можно много где встретить:

#namespace1/__init__.py __import__('pkg_resources').declare_namespace(__name__) 

Модуль pkg_resources поставляется с пакетом setuptools. Здесь смысл такой же, что и в pkgutil — надо, чтобы каждый __init__ файл при каждом размещении namespace1 содержал одинаковое объявление пространства имен и отсутствовал любой другой код. При этом в setup.py надо регистрировать пространство имен namespace_packages=[‘namespace1’]. Более подробное описание создания пакетов выходит за пределы этой статьи.

Кроме того часто можно встретить, такой код

try: __import__('pkg_resources').declare_namespace(__name__) except: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

Здесь логика простая — если не установлен setuptools, то используем pkgutil, который входит в стандартную библиотеку.

Если настроить одним из этих способов пространство имен, то из одного модуля можно звать другой. Например, изменим namespace1/package2/module2

import namespace1.package1.module1 print(var1) 

И далее посмотрим, что будет, если мы по ошибке назвали новый пакет так же как уже существующий и обернули тем же namespace’ом. Например, будут два пакета в разных местах с названием package1.

 namespace1 package1 module1 package1 module2 

В этом случае импортирован будет только первый и доступа к module2 не будет. Пакеты объединить нельзя.

from namespace1.package1 import module1 from namespace1.package1 import module2 #>>ImportError: cannot import name module2
  1. В случае Python старше 3.3 и установки с помощью pip рекомендуется использовать неявное объявление пространства имен.
  2. В случае поддержки версий 2 и 3, а так же установки и с pip и с python setup.py install, рекомендуется вариант с pkgutil.
  3. Вариант pkg_resources рекомендуется, если надо поддерживать старые пакеты, использующие такой метод, или вам надо чтобы пакет был zip-safe.

Источник

Оцените статью