Как мне создать константу в Python? - PullRequest
846 голосов
/ 21 апреля 2010

Есть ли способ объявить константу в Python? В Java мы можем создавать постоянные значения следующим образом:

public static final String CONST_NAME = "Name";

Что эквивалентно приведенному выше объявлению константы Java в Python?

Ответы [ 33 ]

6 голосов
/ 05 декабря 2016

К сожалению, у Питона еще нет констант, и это позор. ES6 уже добавил константы поддержки в JavaScript (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const), поскольку это очень полезная вещь на любом языке программирования. Как и в других ответах в сообществе Python, в качестве констант используется соглашение - переменная верхнего регистра пользователя, но она не защищает от произвольных ошибок в коде. Если вам нравится, вы можете найти полезным решение для одного файла, как следующий (смотрите документацию, как его использовать).

файл constants.py

import collections


__all__ = ('const', )


class Constant(object):
    """
    Implementation strict constants in Python 3.

    A constant can be set up, but can not be changed or deleted.
    Value of constant may any immutable type, as well as list or set.
    Besides if value of a constant is list or set, it will be converted in an immutable type as next:
        list -> tuple
        set -> frozenset
    Dict as value of a constant has no support.

    >>> const = Constant()
    >>> del const.temp
    Traceback (most recent call last):
    NameError: name 'temp' is not defined
    >>> const.temp = 1
    >>> const.temp = 88
    Traceback (most recent call last):
        ...
    TypeError: Constanst can not be changed
    >>> del const.temp
    Traceback (most recent call last):
        ...
    TypeError: Constanst can not be deleted
    >>> const.I = ['a', 1, 1.2]
    >>> print(const.I)
    ('a', 1, 1.2)
    >>> const.F = {1.2}
    >>> print(const.F)
    frozenset([1.2])
    >>> const.D = dict()
    Traceback (most recent call last):
        ...
    TypeError: dict can not be used as constant
    >>> del const.UNDEFINED
    Traceback (most recent call last):
        ...
    NameError: name 'UNDEFINED' is not defined
    >>> const()
    {'I': ('a', 1, 1.2), 'temp': 1, 'F': frozenset([1.2])}
    """

    def __setattr__(self, name, value):
        """Declaration a constant with value. If mutable - it will be converted to immutable, if possible.
        If the constant already exists, then made prevent againt change it."""

        if name in self.__dict__:
            raise TypeError('Constanst can not be changed')

        if not isinstance(value, collections.Hashable):
            if isinstance(value, list):
                value = tuple(value)
            elif isinstance(value, set):
                value = frozenset(value)
            elif isinstance(value, dict):
                raise TypeError('dict can not be used as constant')
            else:
                raise ValueError('Muttable or custom type is not supported')
        self.__dict__[name] = value

    def __delattr__(self, name):
        """Deny against deleting a declared constant."""

        if name in self.__dict__:
            raise TypeError('Constanst can not be deleted')
        raise NameError("name '%s' is not defined" % name)

    def __call__(self):
        """Return all constans."""

        return self.__dict__


const = Constant()


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Если этого недостаточно, см. Полный тестовый сценарий для него.

import decimal
import uuid
import datetime
import unittest

from ..constants import Constant


class TestConstant(unittest.TestCase):
    """
    Test for implementation constants in the Python
    """

    def setUp(self):

        self.const = Constant()

    def tearDown(self):

        del self.const

    def test_create_constant_with_different_variants_of_name(self):

        self.const.CONSTANT = 1
        self.assertEqual(self.const.CONSTANT, 1)
        self.const.Constant = 2
        self.assertEqual(self.const.Constant, 2)
        self.const.ConStAnT = 3
        self.assertEqual(self.const.ConStAnT, 3)
        self.const.constant = 4
        self.assertEqual(self.const.constant, 4)
        self.const.co_ns_ta_nt = 5
        self.assertEqual(self.const.co_ns_ta_nt, 5)
        self.const.constant1111 = 6
        self.assertEqual(self.const.constant1111, 6)

    def test_create_and_change_integer_constant(self):

        self.const.INT = 1234
        self.assertEqual(self.const.INT, 1234)
        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.INT = .211

    def test_create_and_change_float_constant(self):

        self.const.FLOAT = .1234
        self.assertEqual(self.const.FLOAT, .1234)
        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.FLOAT = .211

    def test_create_and_change_list_constant_but_saved_as_tuple(self):

        self.const.LIST = [1, .2, None, True, datetime.date.today(), [], {}]
        self.assertEqual(self.const.LIST, (1, .2, None, True, datetime.date.today(), [], {}))

        self.assertTrue(isinstance(self.const.LIST, tuple))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.LIST = .211

    def test_create_and_change_none_constant(self):

        self.const.NONE = None
        self.assertEqual(self.const.NONE, None)
        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.NONE = .211

    def test_create_and_change_boolean_constant(self):

        self.const.BOOLEAN = True
        self.assertEqual(self.const.BOOLEAN, True)
        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.BOOLEAN = False

    def test_create_and_change_string_constant(self):

        self.const.STRING = "Text"
        self.assertEqual(self.const.STRING, "Text")

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.STRING += '...'

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.STRING = 'TEst1'

    def test_create_dict_constant(self):

        with self.assertRaisesRegexp(TypeError, 'dict can not be used as constant'):
            self.const.DICT = {}

    def test_create_and_change_tuple_constant(self):

        self.const.TUPLE = (1, .2, None, True, datetime.date.today(), [], {})
        self.assertEqual(self.const.TUPLE, (1, .2, None, True, datetime.date.today(), [], {}))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.TUPLE = 'TEst1'

    def test_create_and_change_set_constant(self):

        self.const.SET = {1, .2, None, True, datetime.date.today()}
        self.assertEqual(self.const.SET, {1, .2, None, True, datetime.date.today()})

        self.assertTrue(isinstance(self.const.SET, frozenset))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.SET = 3212

    def test_create_and_change_frozenset_constant(self):

        self.const.FROZENSET = frozenset({1, .2, None, True, datetime.date.today()})
        self.assertEqual(self.const.FROZENSET, frozenset({1, .2, None, True, datetime.date.today()}))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.FROZENSET = True

    def test_create_and_change_date_constant(self):

        self.const.DATE = datetime.date(1111, 11, 11)
        self.assertEqual(self.const.DATE, datetime.date(1111, 11, 11))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.DATE = True

    def test_create_and_change_datetime_constant(self):

        self.const.DATETIME = datetime.datetime(2000, 10, 10, 10, 10)
        self.assertEqual(self.const.DATETIME, datetime.datetime(2000, 10, 10, 10, 10))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.DATETIME = None

    def test_create_and_change_decimal_constant(self):

        self.const.DECIMAL = decimal.Decimal(13123.12312312321)
        self.assertEqual(self.const.DECIMAL, decimal.Decimal(13123.12312312321))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.DECIMAL = None

    def test_create_and_change_timedelta_constant(self):

        self.const.TIMEDELTA = datetime.timedelta(days=45)
        self.assertEqual(self.const.TIMEDELTA, datetime.timedelta(days=45))

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.TIMEDELTA = 1

    def test_create_and_change_uuid_constant(self):

        value = uuid.uuid4()
        self.const.UUID = value
        self.assertEqual(self.const.UUID, value)

        with self.assertRaisesRegexp(TypeError, 'Constanst can not be changed'):
            self.const.UUID = []

    def test_try_delete_defined_const(self):

        self.const.VERSION = '0.0.1'
        with self.assertRaisesRegexp(TypeError, 'Constanst can not be deleted'):
            del self.const.VERSION

    def test_try_delete_undefined_const(self):

        with self.assertRaisesRegexp(NameError, "name 'UNDEFINED' is not defined"):
            del self.const.UNDEFINED

    def test_get_all_defined_constants(self):

        self.assertDictEqual(self.const(), {})

        self.const.A = 1
        self.assertDictEqual(self.const(), {'A': 1})

        self.const.B = "Text"
        self.assertDictEqual(self.const(), {'A': 1, 'B': "Text"})

Преимущества: 1. Доступ ко всем константам для всего проекта 2. Строгий контроль значений констант

Недостатки: 1. Не поддерживается для пользовательских типов и типа 'dict'

Примечания:

  1. Протестировано с Python3.4 и Python3.5 (я использую 'tox' для него)

  2. Среда тестирования:

.

$ uname -a
Linux wlysenko-Aspire 3.13.0-37-generic #64-Ubuntu SMP Mon Sep 22 21:28:38 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
6 голосов
/ 13 октября 2016

Вы можете использовать именованный кортеж в качестве обходного пути для эффективного создания константы, которая работает так же, как статическая конечная переменная в Java (Java-константа). Как обходные пути идут, это вроде элегантно. (Более элегантный подход - просто улучшить язык Python - какой язык позволяет вам переопределить math.pi? - но я отвлекся.)

(Когда я пишу это, я понимаю другой ответ на этот вопрос, названный namedtuple, но я продолжу здесь, потому что я покажу синтаксис, который более точно соответствует тому, что вы ожидаете в Java, так как нет необходимости создайте именованный тип , как вас заставляет namedtuple.)

Следуя вашему примеру, вы помните, что в Java мы должны определить константу внутри некоторого класса ; потому что вы не упомянули имя класса, давайте назовем его Foo. Вот класс Java:

public class Foo {
  public static final String CONST_NAME = "Name";
}

Вот эквивалентный Python.

from collections import namedtuple
Foo = namedtuple('_Foo', 'CONST_NAME')('Name')

Ключевой момент, который я хочу добавить, заключается в том, что вам не нужен отдельный тип Foo («анонимный именованный кортеж» был бы хорош, даже если это звучит как оксюморон), поэтому мы называем наш именованный кортеж _Foo так что, надеюсь, это не ускользнет от импорта модулей.

Вторым моментом здесь является то, что мы немедленно создаем экземпляр именного кортежа, называя его Foo; нет необходимости делать это в отдельном шаге (если вы этого не хотите). Теперь вы можете делать то, что вы можете делать в Java:

>>> Foo.CONST_NAME
'Name'

Но вы не можете присвоить ему:

>>> Foo.CONST_NAME = 'bar'
…
AttributeError: can't set attribute

Подтверждение: я думал, что изобрел подход именованных кортежей, но потом я вижу, что кто-то другой дал аналогичный (хотя и менее компактный) ответ. Затем я также заметил Что такое "именованные кортежи" в Python? , который указывает, что sys.version_info теперь является именованным кортежем, поэтому, возможно, стандартная библиотека Python уже пришла в голову эта идея намного раньше.

Обратите внимание, что, к сожалению (это все еще Python), вы можете полностью стереть назначение Foo:

>>> Foo = 'bar'

(FACEPALM)

Но, по крайней мере, мы предотвращаем изменение значения Foo.CONST_NAME, и это лучше, чем ничего. Удачи.

5 голосов
/ 10 мая 2016

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

my_tuple = (0 """Or any other value""",)

Чтобы проверить значение этой переменной, используйте что-то похожее на это:

if my_tuple[0] == 0:
    #Code goes here

Если вы попытаетесь изменить это значение, возникнет ошибка.

4 голосов
/ 21 апреля 2010

Pythonic способ объявления «констант» в основном является переменной уровня модуля:

RED = 1
GREEN = 2
BLUE = 3

А затем напишите свои классы или функции. Поскольку константы почти всегда являются целыми числами и они также неизменны в Python, у вас очень мало шансов изменить его.

Если, конечно, если вы явно не указали RED = 2.

3 голосов
/ 12 мая 2017

Мы можем создать объект дескриптора.

class Constant:
  def __init__(self,value=None):
    self.value = value
  def __get__(self,instance,owner):
    return self.value
  def __set__(self,instance,value):
    raise ValueError("You can't change a constant")

1) Если мы хотим работать с константами на уровне экземпляра, то:

class A:
  NULL = Constant()
  NUM = Constant(0xFF)

class B:
  NAME = Constant('bar')
  LISTA = Constant([0,1,'INFINITY'])

>>> obj=A()
>>> print(obj.NUM)  #=> 255
>>> obj.NUM =100

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: You can't change a constant

2) если бы мы хотели создавать константы только на уровне класса, мы могли бы использовать метакласс, который служит контейнером для наших констант (наших объектов-дескрипторов); все нисходящие классы наследуют наши константы (наши объекты-дескрипторы) без какого-либо риска, который может быть изменен.

# metaclass of my class Foo
class FooMeta(type): pass

# class Foo
class Foo(metaclass=FooMeta): pass

# I create constants in my metaclass
FooMeta.NUM = Constant(0xff)
FooMeta.NAME = Constant('FOO')

>>> Foo.NUM   #=> 255
>>> Foo.NAME  #=> 'FOO'
>>> Foo.NUM = 0 #=> ValueError: You can't change a constant

Если я создаю подкласс Foo, этот класс наследует константу без возможности их изменения

class Bar(Foo): pass

>>> Bar.NUM  #=> 255
>>> Bar.NUM = 0  #=> ValueError: You can't change a constant
3 голосов
/ 30 апреля 2018

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

* 1003 например *

DAYS_IN_WEEK = 7

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

Это подход, принятый в Python. По той же причине нет ключевого слова * 1009. Добавьте к имени подчеркивание, и вы знаете, что оно предназначено для частного использования. Код может нарушить правило .... так же, как программист может удалить ключевое слово private в любом случае.

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

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

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

Это философия питона.

3 голосов
/ 30 июня 2011

Словари Python являются изменяемыми, поэтому они не кажутся хорошим способом объявления констант:

>>> constants = {"foo":1, "bar":2}
>>> print constants
{'foo': 1, 'bar': 2}
>>> constants["bar"] = 3
>>> print constants
{'foo': 1, 'bar': 3}
3 голосов
/ 12 сентября 2018

Есть более чистый способ сделать это с namedtuple:

from collections import namedtuple


def make_consts(name, **kwargs):
    return namedtuple(name, kwargs.keys())(**kwargs)

Пример использования

CONSTS = make_consts("baz1",
                     foo=1,
                     bar=2)

При таком подходе вы можете использовать пространство имен для ваших констант.

3 голосов
/ 29 октября 2018

Может быть, библиотека pconst вам поможет ( github ).

$ pip install pconst

from pconst import const
const.APPLE_PRICE = 100
const.APPLE_PRICE = 200

[Out] Constant value of "APPLE_PRICE" is not editable.

2 голосов
/ 06 мая 2018

Просто вы можете просто:

STRING_CONSTANT = "hi"
NUMBER_CONSTANT = 89

надеюсь, что все будет намного проще

...