Константы Python: Улучшение управляемости вашего кода

Оглавление

  • Понимание констант и переменных
    • Что такое переменные
    • Что такое константы
    • Зачем использовать константы
    • Когда использовать константы
  • Определение собственных констант в Python
    • Определенные пользователем константы
    • Модульные константы уровня Данных
  • Введение констант в действие
    • Замена магических чисел для удобочитаемости
    • Использование объектов для удобства обслуживания
    • Предоставление значений аргументов по умолчанию
  • Работа с константами в реальном проекте
    • Размещение констант вместе со связанным кодом
    • Создание специального модуля для констант
    • Хранение констант в конфигурационных файлах
    • Обработка констант как переменных окружения
  • Изучение других констант в Python
    • Встроенные константы
    • Внутренние имена дандеров
    • Полезные строковые и математические константы
  • Константы, обозначающие тип
  • Определение строгих констант в Python
    • Атрибут .__slots__
    • Декоратор @property
    • Фабричная функция namedtuple()
    • Декоратор @dataclass
    • Специальный метод .__setattr__()
  • Заключение

В программировании термин константа относится к именам, представляющим значения, которые не меняются во время выполнения программы. Константы являются фундаментальным понятием в программировании, и разработчики Python используют их во многих случаях. Однако в Python нет специального синтаксиса для определения констант. На практике константы Python - это просто переменные, которые никогда не изменяются.

Чтобы предотвратить переназначение программистами имен, которые должны содержать константу, сообщество Python приняло соглашение об именовании: использовать заглавные буквы. Для каждого питониста важно знать, что такое константы, а также зачем и когда их использовать.

В этом учебнике вы узнаете, как:

  • Правильно определять константы в Python
  • Определите некоторые встроенные константы
  • Используйте константы для улучшения читабельности кода, повторного использования и сопровождаемости кода
  • Применять различные подходы к организации и управлению константами в проекте
  • Использование нескольких методов для того, чтобы сделать константы строго постоянными в Python

Научившись определять и использовать константы, вы значительно улучшите читаемость, сопровождаемость и возможность повторного использования вашего кода.

Чтобы извлечь максимум пользы из этого учебника, вам понадобятся базовые знания о Python переменных, функциях, модулях, пакетах и пространствах имен. Вам также понадобится знание основ объектно-ориентированного программирования в Python.

Понимание констант и переменных

Переменные и константы - две исторические и фундаментальные концепции в компьютерном программировании. Большинство языков программирования используют эти понятия для манипулирования данными и эффективной и логичной работы.

Переменные и константы, вероятно, будут присутствовать в каждом проекте, приложении, библиотеке или другом куске кода, который вы когда-либо напишете. Вопрос в том, что такое переменные и константы на практике?

Что такое переменные

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

Переменные обычно имеют описательное имя, которое каким-то образом связано с целевым значением или объектом. Это целевое значение может быть любого типа данных. Так, переменные можно использовать для представления чисел, строк, последовательностей, пользовательских объектов и многого другого.

Вы можете выполнить две основные операции над переменной:

  1. Доступ к его значению
  2. Присвоить ему новое значение

В большинстве языков программирования вы можете получить доступ к значению, связанному с переменной, указав ее имя в коде. Чтобы присвоить новое значение данной переменной, используется оператор assignment, который часто состоит из имени переменной, оператора присваивания и желаемого значения.

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

Что такое константы

В математике также есть понятие константы. Этот термин относится к значению или величине, которая никогда не изменяется. В программировании константы означают имена, связанные со значениями, которые никогда не меняются во время выполнения программы.

Как и переменные, константы в программировании состоят из двух частей: имени и связанного с ним значения. Имя четко описывает суть константы. Значение - это конкретное выражение самой константы.

Как и в случае с переменными, значение, связанное с данной константой, может быть любого типа данных. Таким образом, вы можете определить целочисленные константы, константы с плавающей точкой, символьные константы, строковые константы и многое другое.

После того, как вы определили константу, она позволит вам выполнить над ней только одну операцию. Вы можете только получить доступ к значению константы, но не изменять его со временем. Это отличается от переменной, которая позволяет вам не только получить доступ к ее значению, но и переназначить его.

Вы будете использовать константы для представления значений, которые не будут меняться. Вы найдете много таких значений в повседневном программировании. Несколько примеров: скорость света, количество минут в часе и имя корневой папки проекта.

Зачем использовать константы

В большинстве языков программирования константы защищают вас от случайного изменения их значений где-то в коде, когда вы занимаетесь кодированием в два часа ночи, вызывая неожиданные и трудноотлаживаемые ошибки. Константы также помогают сделать ваш код более читабельным и удобным для сопровождения.

Некоторые преимущества использования констант вместо использования их значений непосредственно в коде включают:

Преимущество

Описание

Улучшенная читаемость

Описательное имя, представляющее заданное значение в программе, всегда более удобочитаемо и ясно, чем само базовое значение. Например, проще прочитать и понять константу с именем MAX_SPEED, чем само конкретное значение скорости.

Четкое сообщение о намерениях

Большинство людей предположат, что это 3.14может относиться к константе Пи . Однако использование имени Pi, pi, или PIболее четко сообщит о ваших намерениях, чем прямое использование значения. Эта практика позволит другим разработчикам быстро и точно понять ваш код.

Лучшая ремонтопригодность

Константы позволяют использовать одно и то же имя для идентификации одного и того же значения во всем коде. Если вам нужно обновить значение константы, вам не нужно изменять каждый экземпляр значения. Вам просто нужно изменить значение в одном месте: определение константы. Это улучшает ремонтопригодность вашего кода.

Меньший риск ошибок

Константа, представляющая заданное значение в программе, менее подвержена ошибкам, чем несколько явных экземпляров значения. Скажем, вы используете разные уровни точности для числа Пи в зависимости от ваших целевых вычислений. Вы явно использовали значения с требуемой точностью для каждого вычисления. Если вам нужно изменить точность в наборе вычислений, то замена значений может быть подвержена ошибкам, поскольку вы можете в конечном итоге изменить неправильные значения. Безопаснее создавать разные константы для разных уровней точности и изменять код в одном месте.

Снижение потребности в отладке

Константы остаются неизменными в течение всего времени существования программы. Поскольку они всегда будут иметь одно и то же значение, они не должны вызывать ошибок и багов. Эта функция может не понадобиться в небольших проектах, но она может иметь решающее значение в крупных проектах с несколькими разработчиками. Разработчикам не придется тратить время на отладку текущего значения любой константы.

Поточно-безопасное хранилище данных

К константам можно только обращаться, но не записывать. Эта функция делает их потокобезопасными объектами, что означает, что несколько потоков могут одновременно использовать константу без риска повреждения или потери базовых данных.

Как вы узнали из этой таблицы, константы являются важным понятием в программировании по уважительной причине. Они могут сделать вашу жизнь более приятной, а ваш код - более надежным, удобным и читаемым. Итак, когда следует использовать константы?

Когда использовать константы

Жизнь, и особенно наука, полна примеров постоянных ценностей, или ценностей, которые никогда не меняются. Несколько примеров включают:

  • 3.141592653589793: Константа, обозначаемая π, в английском языке пишется как Pi, которая представляет собой отношение окружности круга к его диаметру
  • 2.718281828459045: Константа, обозначаемая e и известная как число Эйлера, которое тесно связано с натуральным логарифмом и сложными процентами
  • 3,600: Число секунд в одном часе, которое считается постоянным в большинстве приложений, хотя високосные секунды иногда добавляются для учета изменчивости скорости вращения Земли
  • -273.15: Константа, представляющая абсолютный ноль в градусах Цельсия, которая равна 0 кельвинов по шкале температур Кельвина

Все приведенные выше примеры являются постоянными величинами, которые люди часто используют в жизни и науке. В программировании вы часто будете иметь дело с этими и многими другими подобными величинами, которые вы можете рассматривать и считать константами.

В общем, используйте константу для представления количества, величины, объекта, параметра или любой другой части данных, которые должны оставаться неизменными в течение всего срока службы.

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

До этого момента вы узнали о константах как об общей концепции в жизни, науке и программировании. Теперь пришло время узнать, как Python работает с константами. Во-первых, вы должны знать, что в Python нет специального синтаксиса для определения констант.

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

Примечание: В этом разделе вы сосредоточитесь на определении собственных констант. Однако есть несколько констант, которые встроены в Python. Вы узнаете о них позже.

Тогда как разработчики Python узнают, что данная переменная представляет собой константу? Сообщество Python решило использовать строгое соглашение об именовании для различения переменных и констант. Читайте дальше, чтобы узнать больше!

Определяемые пользователем константы

Чтобы сообщить другим программистам, что данное значение должно рассматриваться как константа, вы должны использовать общепринятое соглашение об именовании идентификатора или имени константы. Имя следует писать заглавными буквами с подчеркиванием, разделяющим слова, как указано в разделе Константы в PEP 8.

Вот несколько примеров пользовательских констант Python:

PI = 3.14
MAX_SPEED = 300
DEFAULT_COLOR = "\033[1;34m"
WIDTH = 20
API_TOKEN = "593086396372"
BASE_URL = "https://api.example.com"
DEFAULT_TIMEOUT = 5
ALLOWED_BUILTINS = ("sum", "max", "min", "abs")
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    ...
]

Обратите внимание, что вы создали эти константы точно так же, как и переменные. Вы использовали описательное имя, оператор присваивания (=) и конкретное значение константы.

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

Примечание: Опять же, Python не поддерживает константы и непереназначаемые имена. Использование заглавных букв - это просто соглашение, и оно не мешает разработчикам присваивать новые значения вашей константе. Поэтому любой программист, работающий над вашим кодом, должен быть осторожен и никогда не писать код, изменяющий значения констант. Запомните это правило, потому что вы также должны следовать ему.

Поскольку константы Python - это просто переменные, и те, и другие следуют схожим правилам именования, с единственным отличием в том, что константы используют только заглавные буквы. Следуя этой идее, имена констант могут быть следующими:

  • Быть любой длины
  • Состоять из заглавных букв (A-Z)
  • Включать цифры (0-9), но не в качестве первого символа
  • Используйте символы подчеркивания (_) для разделения слов или в качестве первого символа

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

В качестве общей рекомендации по именованию избегайте сокращенных имен при определении констант. Цель имени константы - прояснить смысл значения константы, чтобы вы могли использовать его позже. Эта цель требует описательных имен. Избегайте использования однобуквенных имен, необычных сокращений и общих имен типа NUMBER или MAGNITUDE.

Рекомендуется определять константы в верхней части любого .py файла сразу после всех import утверждений. Таким образом, люди, читающие ваш код, сразу поймут назначение констант и ожидаемое обращение с ними.

Модульные константы уровня Дандера

Данные на уровне модуля - это специальные имена, которые начинаются и заканчиваются двойным подчеркиванием. Примерами могут служить такие имена, как __all__, __author__ и __version__. Эти имена обычно рассматриваются как константы в проектах Python.

Примечание: В Python имя dunder - это имя со специальным значением. Оно начинается и заканчивается двойным подчеркиванием, а слово dunder является portmanteau из double underscore.

Согласно руководству по стилю кодирования Python, PEP 8, имена дункеров на уровне модуля должны появляться после docstring модуля и перед любым утверждением import, кроме __future__ imports.

Вот пример модуля, который включает в себя кучу дундер-имен:

# greeting.py

"""This module defines some module-level dunder names."""

from __future__ import barry_as_FLUFL

__all__ = ["greet"]
__author__ = "Real Python"
__version__ = "0.1.0"

import sys

def greet(name="World"):
    print(f"Hello, {name}!")
    print(f"Greetings from version: {__version__}!")
    print(f"Yours, {__author__}!")

В этом примере __all__ заранее определяет список имен, которые Python будет импортировать, когда вы используете конструкцию from module import * import в своем коде. В этом случае кто-то, импортирующий greeting с подстановочным знаком импорта, получит обратно только функцию greet(). У них не будет доступа к __author__, __version__ и другим именам, не перечисленным в __all__.

Примечание: Конструкция from module import * позволяет вам импортировать все имена, определенные в данном модуле, за один раз. Атрибут __all__ ограничивает импортируемые имена только теми, которые содержатся в базовом списке.

Сообщество Python настоятельно отговаривает от этой import конструкции, широко известной как wildcard imports, поскольку она имеет тенденцию загромождать ваше текущее пространство имен именами, которые вы, вероятно, не будете использовать в своем коде.

В отличие от них, __author__ и __version__ имеют значение только для авторов и пользователей кода, а не для самой логики кода. Эти имена должны рассматриваться как константы, поскольку ни один код не должен менять автора или версию во время выполнения программы.

Обратите внимание, что функция greet() получает доступ к именам дункеров, но не изменяет их. Вот как greet() работает на практике:

>>>

>>> from greeting import *

>>> greet()
Hello, World!
Greetings from version: 0.1.0!
Yours, Real Python!

В целом, не существует жестких правил, запрещающих вам определять собственные имена dunder на уровне модуля. Однако документация Python настоятельно предупреждает против использования дундер-имен, отличных от общепринятых и используемых сообществом. Разработчики ядра могут ввести в язык новые имена dunder в будущем без какого-либо предупреждения.

Введение констант в действие

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

В следующих разделах вы найдете примеры того, насколько ценными могут быть константы в вашем повседневном кодировании.

Замена магических чисел для удобочитаемости

В программировании термин магическое число относится к любому числу, которое появляется непосредственно в вашем коде без каких-либо объяснений. Это значение, которое появляется неожиданно, делая ваш код загадочным и трудным для понимания. Магические числа также делают программы менее читабельными и более сложными для сопровождения и обновления.

Например, допустим, у вас есть следующая функция:

def compute_net_salary(hours):
    return hours * 35 * (1 - (0.04 + 0.1))

Можете ли вы заранее сказать, каково значение каждого числа в этом вычислении? Скорее всего, нет. Различные числа в этой функции являются магическими числами, потому что вы не можете надежно вывести их значения из самих чисел.

Посмотрите следующую рефакторинговую версию этой функции:

HOURLY_SALARY = 35
SOCIAL_SECURITY_TAX_RATE = 0.04
FEDERAL_TAX_RATE = 0.10

def compute_net_salary(hours):
    return (
        hours
        * HOURLY_SALARY
        * (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE))
    )

После этих незначительных обновлений ваша функция теперь читается как шарм. Вы и любой другой разработчик, читающий ваш код, можете с уверенностью сказать, что делает эта функция, потому что вы заменили исходные магические числа на константы с соответствующими названиями. Название каждой константы четко объясняет ее соответствующее значение.

Каждый раз, когда вы обнаружите, что используете магическое число, найдите время, чтобы заменить его константой. Имя этой константы должно быть описательным и однозначно объяснять значение целевого магического числа. Такая практика автоматически улучшит читабельность вашего кода.

Использование объектов для удобства обслуживания

Еще один повседневный случай использования констант - когда заданное значение неоднократно появляется в разных частях кода. Если вы будете вставлять конкретное значение в код в каждом требуемом месте, то у вас возникнут проблемы, если вам понадобится изменить значение по какой-либо причине. В этой ситуации вам придется менять значение в каждом месте.

Одновременное изменение целевого значения в нескольких местах чревато ошибками. Даже если вы полагаетесь на функцию редактора Find and Replace, вы можете оставить несколько неизмененных экземпляров значения, что может привести к неожиданным ошибкам и странному поведению в дальнейшем.

Чтобы предотвратить эти неприятные проблемы, вы можете заменить значение правильно названной константой. Это позволит вам установить значение один раз и повторять его во всех необходимых местах. Если вам когда-нибудь понадобится изменить значение константы, то его нужно будет изменить только в одном месте: в определении константы.

Например, допустим, вы пишете класс Circle, и вам нужны методы для вычисления площади круга, периметра и так далее. После нескольких минут кодирования у вас получился следующий класс:

# circle.py

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius**2

    def perimeter(self):
        return 2 * 3.14 * self.radius

    def projected_volume(self):
        return 4/3 * 3.14 * self.radius**3

    def __repr__(self):
        return f"{self.__class__.__name__}(radius={self.radius})"

Этот пример раскрывает, как приблизительное значение числа Пи (3.14) было записано как магическое число в нескольких методах вашего класса Circle. Почему такая практика представляет собой проблему? Допустим, вам нужно увеличить точность Pi. Тогда вам придется вручную изменить значение по крайней мере в трех разных местах, что утомительно и чревато ошибками, что делает ваш код сложным для сопровождения.

Примечание: Как правило, вам не нужно определять Pi самостоятельно. Python поставляется с некоторыми встроенными константами, включая Pi. Вы увидите, как воспользоваться ими позже.

Использование именованной константы для хранения значения Pi - отличный подход к решению этих проблем. Вот улучшенная версия приведенного выше кода:

# circle.py

PI = 3.14

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return PI * self.radius**2

    def perimeter(self):
        return 2 * PI * self.radius

    def projected_volume(self):
        return 4/3 * PI * self.radius**3

    def __repr__(self):
        return f"{self.__class__.__name__}(radius={self.radius})"

В этой версии Circle для замены магического числа используется глобальная константа PI. Этот код имеет несколько преимуществ по сравнению с оригинальным кодом. Если вам нужно увеличить точность Pi, то достаточно обновить значение константы PI в начале файла. Это обновление немедленно отразится на остальной части кода, не требуя никаких дополнительных действий с вашей стороны.

Примечание: Константы не должны изменяться во время выполнения вашего кода. Однако во время разработки вы можете изменять и подстраивать константы в соответствии с вашими потребностями. Обновление точности числа Пи в вашем классе Circle является хорошим примером того, почему вам может понадобиться изменить значение константы во время разработки вашего кода.

Другим преимуществом является то, что теперь ваш код стал более читабельным и понятным. Название константы не требует пояснений и отражает принятую математическую терминологию.

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

Предоставление значений аргументов по умолчанию

Использование именованных констант для предоставления значений аргументов по умолчанию функциям, методам и классам - еще одна распространенная практика в Python. Есть много примеров такой практики в стандартной библиотеке Python .

Например, модуль zipfile предоставляет инструменты для создания, чтения, записи, добавления и перечисления файлов ZIP. Наиболее важным классом в этом модуле является ZipFile. С помощью ZipFile вы сможете эффективно и быстро работать с ZIP-файлами.

Конструктор класса ZipFile принимает аргумент compression, который позволяет выбрать один из нескольких доступных методов сжатия данных. Этот аргумент является optional и имеет ZIP_STORED значение по умолчанию, что означает, что ZipFile по умолчанию не сжимает входные данные.

В данном примере ZIP_STORED - это константа, определенная в zipfile. Константа содержит числовое значение для несжатых данных. Вы также найдете другие методы сжатия, представленные именованными константами, например, ZIP_DEFLATED для алгоритма сжатия Deflate.

Аргумент compression в конструкторе класса ZipFile является хорошим примером использования констант для предоставления значений аргументов по умолчанию, когда у вас есть аргумент, который может принимать только ограниченное количество допустимых значений.

Еще один пример того, как константы могут пригодиться в качестве значений аргументов по умолчанию, - это когда у вас есть несколько функций с повторяющимся аргументом. Допустим, вы разрабатываете приложение, которое подключается к локальной базе данных SQLite. Ваше приложение использует следующий набор функций для управления базой данных:

import sqlite3
from sqlite3 import Error

def create_database(db_path):
    # Code to create the initial database goes here...

def create_connection(db_path):
    # Code to create a database connection goes here...

def backup_database(db_path):
    # Code to back up the database goes here...

Эти функции выполняют различные действия над вашей базой данных SQLite. Обратите внимание, что все ваши функции имеют общий аргумент db_path.

Во время разработки приложения вы решили указать путь к базе данных по умолчанию для ваших функций, чтобы их можно было быстро протестировать. В этом случае вы можете напрямую использовать путь в качестве значения по умолчанию для аргумента db_path.

Однако, лучше использовать именованную константу для указания пути к базе данных по умолчанию:

import sqlite3
from sqlite3 import Error

DEFAULT_DB_PATH = "/path/to/database.sqlite"

def create_database(db_path=DEFAULT_DB_PATH):
    # Code to create the initial database goes here...

def create_connection(db_path=DEFAULT_DB_PATH):
    # Code to create a database connection goes here...

def backup_database(db_path=DEFAULT_DB_PATH):
    # Code to back up the database goes here...

Это небольшое обновление позволяет вам быстро протестировать ваше приложение, нацелившись на образец базы данных во время разработки. Оно также улучшает сопровождаемость вашего кода, поскольку вы можете повторно использовать эту константу в других функциях, связанных с базой данных, которые появятся в будущих версиях вашего приложения.

Наконец, встречаются ситуации, когда вы хотите передать классу, методу или функции объект с определенным поведением. Эта практика обычно известна как duck typing и является фундаментальным принципом Python. Теперь предположим, что ваш код позаботится о предоставлении стандартной реализации требуемого объекта. Если вашим пользователям нужен пользовательский объект, то они должны предоставить его сами.

В этой ситуации вы можете использовать константу для определения объекта по умолчанию, а затем передать эту константу в качестве значения аргумента по умолчанию в целевой класс, метод или функцию. Посмотрите следующий пример гипотетического класса FileReader:

# file_handler.py

from readers import DEFAULT_READER

class FileHandler:
    def __init__(self, file, reader=DEFAULT_READER):
        self._file = file
        self._reader = reader

    def read(self):
        self._reader.read(self._file)

    # FileHandler implementation goes here...

Этот класс предоставляет возможность манипулировать различными типами файлов. Метод .read() использует введенный объект reader для чтения входного файла file в соответствии с его определенным форматом.

Вот игрушечная реализация класса читателя:

# readers.py

class _DefaultReader:
    def read(self, file):
        with open(file, mode="r", encoding="utf-8") as file_obj:
            for line in file_obj:
                print(line)

DEFAULT_READER = _DefaultReader()

Метод .read() в этом примере принимает путь к файлу, открывает его и печатает его содержимое на экране строка за строкой. Этот класс будет играть роль читалки по умолчанию. Последний шаг - создание константы DEFAULT_READER для хранения экземпляра вашего устройства чтения по умолчанию. Вот и все! У вас есть класс, который обрабатывает входные файлы, а также класс-помощник, который предоставляет читалку по умолчанию.

Ваши пользователи также могут кодировать пользовательские считыватели. Например, они могут написать считыватели для файлов CSV и JSON. После того, как они напишут заданный считыватель, они могут передать его в конструктор класса FileHandler и использовать полученный экземпляр для обработки файлов, использующих целевой формат файла считывателя.

Работа с константами в реальном проекте

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

  • Файл такой же, как и код, который их использует
  • А выделенный модуль для общепроектных констант
  • А файл конфигурации
  • Некоторые переменные окружения

В следующих разделах вы напишете несколько практических примеров, демонстрирующих описанные выше стратегии управления константами.

Помещение констант вместе со связанным кодом

Первая и, возможно, наиболее естественная стратегия организации и управления константами - это определение их вместе с кодом, который их использует. При таком подходе вы будете определять константы в верхней части модуля, содержащего соответствующий код.

Например, допустим, вы создаете пользовательский модуль для выполнения вычислений, и вам нужно использовать математические константы, такие как Pi, число Эйлера и некоторые другие. В этом случае вы можете сделать что-то вроде этого:

# calculations.py

"""This module implements custom calculations."""

# Imports go here...
import numpy as np

# Constants go here...
PI = 3.141592653589793
EULER_NUMBER = 2.718281828459045
TAU = 6.283185307179586

# Your custom calculations start here...
def circular_land_area(radius):
    return PI * radius**2

def future_value(present_value, interest_rate, years):
    return present_value * EULER_NUMBER ** (interest_rate * years)

# ...

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

Примечание: Если вы хотите явно указать, что константа должна использоваться только в содержащем ее модуле, то вы можете добавить к ее имени символ подчеркивания (_). Например, вы можете сделать что-то вроде _PI = 3.141592653589793. Это подчеркивание маркирует имя как non-public, что означает, что код пользователя не должен использовать это имя напрямую.

Помещение констант вместе с кодом, который их использует, - это быстрая и подходящая стратегия для констант узкого назначения, которые относятся только к одному модулю в данном проекте. В этом случае вы, вероятно, не будете использовать константы за пределами самого содержащего модуля.

Создание выделенного модуля для констант

Другой распространенной стратегией организации и управления константами является создание выделенного модуля, в котором они будут храниться. Эта стратегия подходит для констант, которые используются во многих модулях и даже пакетах данного проекта.

Главная идея этой стратегии заключается в создании интуитивно понятного и уникального пространства имен для констант. Чтобы применить эту стратегию к примеру с расчетами, можно создать пакет Python, содержащий следующие файлы:

calc/
├── __init__.py
├── calculations.py
└── constants.py

Файл __init__.py превратит каталог calc/ в пакет Python. Затем вы можете добавить следующее содержимое в ваш constants.py файл:

# constants.py

"""This module defines project-level constants."""

PI = 3.141592653589793
EULER_NUMBER = 2.718281828459045
TAU = 6.283185307179586

Когда вы добавили этот код в constants.py, вы можете импортировать модуль, когда вам понадобится использовать любую из ваших констант:

# calculations.py

"""This module implements custom calculations."""

# Imports go here...
import numpy as np

from . import constants

# Your custom calculations start here...
def circular_land_area(radius):
    return constants.PI * radius**2

def future_value(present_value, interest_rate, years):
    return present_value * constants.EULER_NUMBER ** (interest_rate * years)

# ...

Обратите внимание, что вы импортируете модуль constants непосредственно из пакета calc, используя относительный импорт . Затем вы используете полные имена для доступа к любым требуемым константам в ваших вычислениях. Такая практика улучшает передачу намерений. Теперь совершенно ясно, что PI и EULER_NUMBER являются константами в вашем проекте благодаря префиксу constants.

Чтобы использовать ваш модуль calculations, вы можете сделать примерно следующее:

>>>

>>> from calc import calculations
>>> calculations.circular_land_area(100)
31415.926535897932

>>> from calc.calculations import circular_land_area
>>> circular_land_area(100)
31415.926535897932

Теперь ваш модуль calculations живет внутри пакета calc. Это означает, что если вы хотите использовать функции в calculations, то вам нужно импортировать calculations из calc. Вы также можете импортировать функции напрямую, ссылаясь на пакет и модуль, как вы сделали во втором примере выше.

Хранение констант в файлах конфигурации

Теперь предположим, что вы хотите пойти дальше, когда дело доходит до внешнего использования констант данного проекта. Вам может понадобиться сохранить все константы вне исходного кода проекта. Для этого вы можете использовать внешний файл конфигурации.

Вот пример того, как перенести константы в конфигурационный файл:

; constants.ini

[CONSTANTS]
PI=3.141592653589793
EULER_NUMBER=2.718281828459045
TAU=6.283185307179586

Этот файл использует формат INI-файла. Вы можете прочитать этот тип файла с помощью модуля configparser из стандартной библиотеки.

Теперь вернитесь к calculations.py и обновите его, чтобы он выглядел примерно так:

# calculations.py

"""This module implements custom calculations."""

# Imports go here...
from configparser import ConfigParser

import numpy as np

constants = ConfigParser()
constants.read("path/to/constants.ini")

# Your custom calculations start here...
def circular_land_area(radius):
    return float(constants.get("CONSTANTS", "PI")) * radius**2

def future_value(present_value, interest_rate, years):
    return (
        present_value * float(constants.get(
            "CONSTANTS",
            "EULER_NUMBER"
        ))) ** (interest_rate * years)

# ...

В этом примере ваш код сначала считывает файл конфигурации и сохраняет полученный объект ConfigParser в глобальной переменной constants. Вы также можете назвать эту переменную CONSTANTS и использовать ее глобально как константу. Затем вы обновляете свои вычисления, чтобы считывать константы из самого объекта конфигурации.

Обратите внимание, что объекты ConfigParser хранят параметры конфигурации в виде строк, поэтому для преобразования значений в числа необходимо использовать встроенную функцию float().

Эта стратегия может оказаться полезной, когда вы создаете приложение графического пользовательского интерфейса (GUI) и вам нужно задать некоторые параметры для определения формы и размера окон приложения, например, при загрузке и отображении GUI.

Обработка констант как переменных среды

Другая полезная стратегия работы с константами - определить их как системные переменные, если вы работаете в Windows, или переменные среды, если вы работаете в macOS или Linux.

Этот подход обычно используется для настройки развертывания в различных окружениях. Вы также можете использовать переменные окружения для констант, которые связаны с рисками безопасности и не должны напрямую фиксироваться в исходном коде. Примерами таких констант являются учетные данные аутентификации, маркеры доступа к API и т.д.

Примечание: Следует быть осторожным при использовании переменных окружения для конфиденциальной информации, поскольку они могут быть случайно раскрыты в журналах или дочерних процессах. Все облачные провайдеры предлагают некоторые виды управления секретами, которые являются более безопасными.

Чтобы использовать эту стратегию, вы должны сначала экспортировать ваши константы как переменные среды или системные переменные в вашей операционной системе. Существует, по крайней мере, два способа сделать это:

  1. Вручную экспортируйте константы в текущей сессии оболочки
  2. Добавьте ваши константы в конфигурационный файл оболочки

Первая техника довольно быстрая и практичная. Вы можете использовать его для проведения быстрых тестов вашего кода. Например, допустим, вам нужно экспортировать токен API в качестве системной переменной или переменной окружения. В этом случае вам просто нужно выполнить следующую команду:

C:\> set API_TOKEN="593086396372"

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

Если вы работаете под Windows, то ознакомьтесь с разделом Configuring Environment Variables в Your Python Coding Environment on Windows: Руководство по настройке, чтобы узнать, как создавать системные переменные. Следуя инструкциям в этом руководстве, добавьте системную переменную API_TOKEN со значением 593086396372.

Если вы работаете в Linux или macOS, перейдите в домашнюю папку и откройте файл конфигурации вашей оболочки. Открыв этот файл, добавьте в его конец следующую строку:

# .bashrc

export API_TOKEN="593086396372"

Linux и macOS автоматически загружают соответствующий файл конфигурации оболочки при каждом запуске терминала или окна командной строки. Таким образом, вы гарантируете, что переменная API_TOKEN всегда доступна в вашей системе.

После того, как вы определили необходимые переменные окружения для вашей константы Python, вам нужно загрузить их в ваш код. Для этого вы можете использовать словарь environ из модуля Python os. Ключи и значения environ - это строки, представляющие переменные окружения и их значения, соответственно.

Ваша константа API_TOKEN теперь присутствует в словаре environ. Поэтому вы можете прочитать ее оттуда всего двумя строками кода:

>>>

>>> import os

>>> os.environ["API_TOKEN"]
'593086396372'

Использование переменных среды для хранения констант и словаря os.environ для их чтения в коде - эффективный способ настройки констант, зависящих от среды, в которой развернуто ваше приложение. Это особенно полезно при работе с облаком, поэтому держите эту технику в своем наборе инструментов Python.

Изучение других констант в Python

Помимо пользовательских констант, Python также определяет несколько внутренних имен, которые можно рассматривать как константы. Некоторые из этих имен являются строгими константами, то есть их нельзя изменить после запуска интерпретатора. Это относится, например, к константе __debug__.

В следующих разделах вы узнаете о некоторых внутренних именах Python, которые можно и нужно рассматривать как константы в вашем коде. Для начала вы рассмотрите некоторые встроенные константы и постоянные значения.

Встроенные константы

Согласно документации Python, "небольшое количество констант живет во встроенном пространстве имен" (Источник). Первые две константы, перечисленные в документации, это True и False, которые являются значениями Python Boolean. Эти два значения также являются экземплярами intTrue имеет значение 1, а False имеет значение 0:

>>>

>>> True
True
>>> False
False

>>> isinstance(True, int)
True
>>> isinstance(False, int)
True

>>> int(True)
1
>>> int(False)
0

>>> True = 42
    ...
SyntaxError: cannot assign to True

>>> True is True
True
>>> False is False
True

Обратите внимание, что имена True и False являются строгими константами. Другими словами, они не могут быть переназначены. Если вы попытаетесь переназначить их, то получите SyntaxError. В Python эти два значения также являются объектами singleton, то есть существует только один экземпляр каждого из них. Вот почему оператор identity operator (is) возвращает True в последних примерах выше.

Другим важным и часто встречающимся постоянным значением является None, которое в Python представляет собой нулевое значение. Это постоянное значение пригодится, когда вы захотите выразить идею nullability. Как и True и False, None также является синглтоном и строгим константным объектом, который не может быть переназначен:

>>>

>>> None is None
True

>>> None = 42
    ...
SyntaxError: cannot assign to None

None весьма полезен в качестве значения аргумента по умолчанию в функциях, методах и конструкторах классов. Обычно он используется для сообщения о том, что переменная пуста. Внутри Python использует None в качестве неявного возвращаемого значения функций, которые не имеют явного return оператора .

Литерал многоточия (...) - это еще одно постоянное значение в Python. Это специальное значение совпадает с Ellipsis и является единственным экземпляром типа types.EllipsisType:

>>>

>>> Ellipsis
Ellipsis

>>> ...
Ellipsis

>>> ... is Ellipsis
True

Вы можете использовать Ellipsis в качестве заполнителя для ненаписанного кода. Вы также можете использовать его для замены оператора pass. В подсказках типов литерал ... передает идею коллекции данных неизвестной длины с единым типом:

>>>

>>> def do_something():
...     ...  # TODO: Implement this function later
...

>>> class CustomException(Exception): ...
...
>>> raise CustomException("some error message")
Traceback (most recent call last):
    ...
CustomException: some error message

>>> # A tuple of integer values
>>> numbers: tuple[int, ...]

Постоянное значение Ellipsis может пригодиться во многих ситуациях и поможет вам сделать ваш код более читабельным благодаря своей семантической эквивалентности английскому знаку препинания многоточия (...).

Еще одной интересной и потенциально полезной встроенной константой является __debug__, как вы уже узнали в начале этого раздела. В Python __debug__ - это булева константа, которая по умолчанию имеет значение True. Это строгая константа, поскольку вы не можете изменить ее значение после запуска интерпретатора:

>>>

>>> __debug__
True

>>> __debug__ = False
    ...
SyntaxError: cannot assign to __debug__

Константа __debug__ тесно связана с утверждением assert. Короче говоря, если __debug__ равно True, то все ваши утверждения assert будут выполняться. Если __debug__ равно False, то ваши операторы assert будут отключены и не будут выполняться вообще. Эта функция может немного повысить производительность вашего производственного кода.

Примечание: Хотя __debug__ также имеет имя dunder, оно является строгой константой, поскольку вы не можете изменить его значение после запуска интерпретатора. В отличие от этого, внутренние имена dunder в разделе ниже должны рассматриваться как константы, но не являются строгими константами. Вы можете изменять их значения во время выполнения кода. Однако такая практика может быть сложной и потребует продвинутых знаний.

Чтобы изменить значение __debug__ на False, необходимо запустить Python в оптимизированном режиме с помощью опций командной строки -O или -OO, которые обеспечивают два уровня оптимизации bytecode. Оба уровня генерируют оптимизированный байткод Python, не содержащий утверждений.

Внутренние имена Дандера

Python также имеет широкий набор внутренних дундер-имен, которые вы можете рассматривать как константы. Поскольку таких специальных имен несколько, в этом учебнике вы узнаете только о __name__ и __file__.

Примечание: Чтобы глубже изучить другие названия дундеров в Python и их значение для языка, ознакомьтесь с официальной документацией о модели данных Python .

Атрибут __name__ тесно связан с тем, как вы запускаете данный фрагмент кода. При импорте модуля Python внутренне устанавливает __name__ в строку, содержащую имя импортируемого модуля.

Запустите свой редактор кода и создайте следующий пример модуля:

# sample_name.py

print(f"The type of __name__ is: {type(__name__)}")
print(f"The value of __name__ is: {__name__}")

После того, как вы установили этот файл, вернитесь в окно командной строки и выполните следующую команду:

$ python -c "import sample_name"
The type of __name__ is: <class 'str'>
The value of __name__ is: sample_name

С помощью ключа -c можно выполнить небольшой фрагмент кода Python в командной строке. В этом примере вы импортируете модуль sample_name, который печатает несколько сообщений на экран. Первое сообщение сообщает, что __name__ имеет тип str, или строка. Второе сообщение показывает, что __name__ был установлен в sample_name, что является именем модуля, который вы только что импортировали.

Альтернативно, если взять sample_name.py и запустить его как скрипт , то Python установит __name__ в строку "__main__" . Чтобы подтвердить этот факт, выполните следующую команду:

$ python sample_name.py
The type of __name__ is: <class 'str'>
The value of __name__ is: __main__

Обратите внимание, что теперь __name__ содержит строку "__main__". Такое поведение указывает на то, что вы запустили файл непосредственно как исполняемую программу Python.

Атрибут __file__ будет содержать путь к файлу, который Python в настоящее время импортирует или выполняет. Вы можете использовать __file__ изнутри данного модуля, когда вам нужно получить путь к самому модулю.

В качестве примера того, как работает __file__, создайте следующий модуль:

# sample_file.py

print(f"The type of __file__ is: {type(__file__)}")
print(f"The value of __file__ is: {__file__}")

Если вы импортируете модуль sample_file в свой код Python, то __file__ будет хранить путь к содержащему его модулю в вашей файловой системе. Проверьте это, выполнив следующую команду:

$ python -c "import sample_file"
The type of __file__ is: <class 'str'>
The value of __file__ is: /path/to/sample_file.py

Аналогично, если вы запустите sample_file.py как исполняемую программу Python, то получите тот же результат, что и раньше:

$ python sample_file.py
The type of __file__ is: <class 'str'>
The value of __file__ is: /path/to/sample_file.py

Кроче говоря, Python задает __file__ путь к модулю, из которого вы используете или обращаетесь к этому атрибуту.

Полезные строковые и математические константы

В стандартной библиотеке вы найдете множество полезных констант. Некоторые из них тесно связаны с определенными модулями, функциями и классами. Другие являются более общими, и вы можете использовать их в различных сценариях. Так обстоит дело с некоторыми константами, связанными с математикой и строками, которые можно найти в модулях math и string соответственно.

Модуль math предоставляет следующие константы:

>>>

>>> import math

>>> # Euler's number (e)
>>> math.e
2.718281828459045

>>> # Pi (π)
>>> math.pi
3.141592653589793

>>> # Infinite (∞)
>>> math.inf
inf

>>> # Not a number (NaN)
>>> math.nan
nan

>>> # Tau (τ)
>>> math.tau
6.283185307179586

Эти константы пригодятся вам при написании математического кода или даже кода, который просто использует их для выполнения определенных вычислений, как, например, ваш класс Circle, описанный в разделе Reusing Objects for Maintainability.

Вот обновленная реализация Circle, использующая math.pi вместо вашей пользовательской константы PI:

# circle.py

import math

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius**2

    def perimeter(self):
        return 2 * math.pi * self.radius

    def projected_volume(self):
        return 4/3 * math.pi * self.radius**3

    def __repr__(self):
        return f"{self.__class__.__name__}(radius={self.radius})"

Эта обновленная версия Circle более удобочитаема, чем ваша первоначальная версия, потому что она предоставляет больше контекста о том, откуда взялась константа Pi, давая понять, что это константа, связанная с математикой.

Преимущество константы math.pi заключается также в том, что если вы используете старую версию Python, то получите 32-битную версию Pi. Напротив, если вы используете Circle в современной версии Python, то получите 64-битную версию Pi. Таким образом, ваша программа будет самоадаптироваться к конкретной среде выполнения.

Модуль string также определяет несколько полезных строковых констант. В таблице ниже приведены имя и значение каждой константы:

Name Value
ascii_lowercase abcdefghijklmnopqrstuvwxyz
ascii_uppercase ABCDEFGHIJKLMNOPQRSTUVWXYZ
ascii_letters ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
digits 0123456789
hexdigits 0123456789abcdefABCDEF
octdigits 01234567
punctuation !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
whitespace Комбинация пробела, табуляции, новой строки, возврата каретки и переноса страницы
printable Комбинация digitsascii_letterspunctuation и whitespace

Эти константы, связанные со строками, могут пригодиться во многих ситуациях. Вы можете использовать их при обработке большого количества строк, работе с регулярными выражениями, обработке естественного языка и т.д.

Константы с указанием типа

Начиная с Python 3.8, модуль typing включает в себя класс Final, который позволяет аннотировать константы. Если вы используете этот класс при определении констант, то вы сообщаете статическим программам проверки типов, таким как mypy, что ваши константы не должны быть переназначены. Таким образом, программа проверки типов поможет вам обнаружить несанкционированные присваивания ваших констант.

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

from typing import Final

MAX_SPEED: Final[int] = 300
DEFAULT_COLOR: Final[str] = "\033[1;34m"
ALLOWED_BUILTINS: Final[tuple[str, ...]] = ("sum", "max", "min", "abs")

# Later in your code...
MAX_SPEED = 450  # Cannot assign to final name "MAX_SPEED" mypy(error)

Класс Final представляет собой специальную конструкцию типизации, которая указывает средствам проверки типов сообщить об ошибке, если рассматриваемое имя будет переназначено в какой-то момент вашего кода. Обратите внимание, что даже если вы получаете сообщение об ошибке от программы проверки типов, Python изменяет значение MAX_SPEED. Таким образом, Final не предотвращает случайное переназначение констант во время выполнения программы.

Определение строгих констант в Python

До этого момента вы многое узнали о программировании и константах Python. Теперь вы знаете, что Python не поддерживает строгие константы. В нем есть только переменные. Поэтому сообщество Python приняло соглашение об использовании заглавных букв для обозначения того, что данная переменная на самом деле является константой.

Итак, в Python нет констант. Скорее, у вас есть переменные, которые никогда не меняются. Это может стать проблемой, если вы работаете над большим проектом Python с большим количеством программистов разного уровня. В такой ситуации было бы неплохо иметь механизм, гарантирующий строгие константы - константы, которые никто не может изменить после запуска программы.

Поскольку Python - довольно гибкий язык программирования, вы найдете несколько способов достичь цели сделать константу неизменной. В следующих нескольких разделах вы узнаете о некоторых из этих способов. Все они подразумевают создание пользовательского класса и использование его в качестве пространства имен для констант.

Почему вы должны использовать класс в качестве пространства имен для ваших констант? В Python любое имя может быть переименовано по своему усмотрению. На уровне модуля у вас нет соответствующих инструментов, чтобы предотвратить это. Поэтому необходимо использовать класс, потому что классы предоставляют гораздо больше инструментов настройки, чем модули.

В следующих разделах вы узнаете о нескольких различных способах использования класса в качестве пространства имен для строгих констант.

Атрибут .__slots__ Атрибут

Классы Python позволяют определить специальный атрибут класса, называемый .__slots__. Этот атрибут будет содержать последовательность имен, которые будут работать как атрибуты экземпляра.

Вы не сможете добавить новый атрибут экземпляра в класс с атрибутом .__slots__, поскольку .__slots__ препятствует созданию атрибута экземпляра .__dict__. Кроме того, отсутствие атрибута .__dict__ подразумевает оптимизацию с точки зрения потребления памяти.

Используя .__slots__, вы можете создать класс, который работает как пространство имен для констант, доступных только для чтения:

>>>

>>> class ConstantsNamespace:
...     __slots__ = ()
...     PI = 3.141592653589793
...     EULER_NUMBER = 2.718281828459045
...

>>> constants = ConstantsNamespace()

>>> constants.PI
3.141592653589793
>>> constants.EULER_NUMBER
2.718281828459045

>>> constants.PI = 3.14
Traceback (most recent call last):
    ...
AttributeError: 'ConstantsNamespace' object attribute 'PI' is read-only

В этом примере вы определяете ConstantsNamespace. Атрибут класса .__slots__ содержит пустой кортеж, что означает, что экземпляры этого класса не будут иметь атрибутов. Затем вы определяете свои константы как атрибуты класса.

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

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

Декоратор @property Декоратор

Вы также можете воспользоваться преимуществами @property декоратора для создания класса, который работает как пространство имен для ваших констант. Для этого достаточно определить константы как свойства, не предоставляя им метод сеттера:

>>>

>>> class ConstantsNamespace:
...     @property
...     def PI(self):
...         return 3.141592653589793
...     @property
...     def EULER_NUMBER(self):
...         return 2.718281828459045
...

>>> constants = ConstantsNamespace()

>>> constants.PI
3.141592653589793
>>> constants.EULER_NUMBER
2.718281828459045

>>> constants.PI = 3.14
Traceback (most recent call last):
    ...
AttributeError: can't set attribute 'PI'

Поскольку вы не предоставляете методы установки для свойств PI и EULER_NUMBER, они являются свойствами только для чтения. Это означает, что вы можете только доступ к их значениям. Невозможно присвоить новое значение ни одному из них. Если вы попытаетесь это сделать, то получите ошибку AttributeError.

Функция namedtuple() Фабрика

Модуль collections в Python предоставляет заводскую функцию под названием namedtuple(). Эта функция позволяет создавать подклассы кортежей , которые позволяют использовать <именованные поля и нотацию <точка для доступа к их элементам, как в tuple_obj.attribute.

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

Вот как это сделать:

>>>

>>> from collections import namedtuple

>>> ConstantsNamespace = namedtuple(
...     "ConstantsNamespace", ["PI", "EULER_NUMBER"]
... )
>>> constants = ConstantsNamespace(3.141592653589793, 2.718281828459045)

>>> constants.PI
3.141592653589793
>>> constants.EULER_NUMBER
2.718281828459045

>>> constants.PI = 3.14
Traceback (most recent call last):
    ...
AttributeError: can't set attribute

В этом примере ваши константы играют роль полей в базовом именованном кортеже ConstantsNamespace. После создания экземпляра именованного кортежа, constants, вы можете обращаться к своим константам, используя точечную нотацию, как в примере constants.PI.

Поскольку кортежи неизменяемы, у вас нет возможности изменить значение любого поля. Таким образом, ваш объект кортежа с именем constants является полноценным пространством имен строгих констант.

Декоратор @dataclass Декоратор

Классы данных - это классы, которые содержат в основном данные, как следует из их названия. Они также могут иметь методы, но это не является их основной целью. Чтобы создать класс данных, необходимо использовать декоратор @dataclass из модуля dataclasses.

Как вы можете использовать этот тип класса для создания пространства имен строгих констант? Декоратор @dataclass принимает аргумент frozen, который позволяет пометить класс данных как неизменяемый. Если он неизменяемый, то после создания экземпляра данного класса данных у вас не будет возможности изменить атрибуты экземпляра.

Вот как вы можете использовать класс данных для создания пространства имен, содержащего ваши константы:

>>>

>>> from dataclasses import dataclass

>>> @dataclass(frozen=True)
... class ConstantsNamespace:
...     PI = 3.141592653589793
...     EULER_NUMBER = 2.718281828459045
...

>>> constants = ConstantsNamespace()

>>> constants.PI
3.141592653589793
>>> constants.EULER_NUMBER
2.718281828459045

>>> constants.PI = 3.14
Traceback (most recent call last):
    ...
dataclasses.FrozenInstanceError: cannot assign to field 'PI'

В этом примере сначала импортируется декоратор @dataclass. Затем вы используете этот декоратор, чтобы превратить ConstantsNamespace в класс данных. Чтобы сделать класс данных неизменяемым, вы устанавливаете аргумент frozen в True. Наконец, вы определяете ConstantsNamespace с вашими константами в качестве атрибутов класса.

Вы можете создать экземпляр этого класса и использовать его в качестве пространства имен констант. Опять же, вы можете получить доступ ко всем константам, но не можете изменить их значения, потому что класс данных заморожен.

Специальный метод .__setattr__()

Классы Python позволяют определить специальный метод .__setattr__(). Этот метод позволяет настраивать процесс присвоения атрибутов, поскольку Python автоматически вызывает этот метод при каждом присвоении атрибутов.

На практике вы можете переопределить .__setattr__(), чтобы предотвратить все переназначения атрибутов и сделать ваши атрибуты неизменяемыми. Вот как вы можете переопределить этот метод, чтобы создать класс, который работает как пространство имен для ваших констант:

>>>

>>> class ConstantsNamespace:
...     PI = 3.141592653589793
...     EULER_NUMBER = 2.718281828459045
...     def __setattr__(self, name, value):
...         raise AttributeError(f"can't reassign constant '{name}'")
...

>>> constants = ConstantsNamespace()

>>> constants.PI
3.141592653589793
>>> constants.EULER_NUMBER
2.718281828459045

>>> constants.PI = 3.14
Traceback (most recent call last):
    ...
AttributeError: can't reassign constant 'PI'

Ваша пользовательская реализация .__setattr__() не выполняет никаких операций присвоения атрибутов класса. Она просто выдает ошибку AttributeError, когда вы пытаетесь установить какой-либо атрибут. Эта реализация делает атрибуты неизменяемыми. Опять же, ваш ConstantsNamespace ведет себя как пространство имен для констант.

Заключение

Теперь вы знаете, что такое константы, а также зачем и когда их использовать в коде. Вы также знаете, что в Python нет строгих констант. Сообщество Python использует прописные буквы в качестве соглашения об именовании, чтобы сообщить, что переменная должна использоваться как константа. Это соглашение об именовании помогает предотвратить изменение переменных, которые должны быть постоянными, другими разработчиками.

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

В этом уроке вы узнали, как:

  • Определите Python константы в своем коде
  • Определите и поймите некоторые встроенные константы
  • Улучшите читаемость кода, возможность повторного использования и поддержания с помощью констант. Использовать различные стратегии для организации и управления константами в реальном проекте. Применяйте различные техники, чтобы сделать ваши константы Python строго постоянными
  • Зная, что такое константы, почему они важны и когда их следует использовать, вы можете немедленно приступить к улучшению читабельности, сопровождаемости и возможности повторного использования вашего кода. Попробуйте!

Вернуться на верх