class const(object):def __init__(self, val):super(const, self).__setattr__("value", val)def __setattr__(self, name, val):raise ValueError("Trying to change a constant value", self)
要包装字符串:
>>> constObj = const("Try to change me")>>> constObj.value'Try to change me'>>> constObj.value = "Changed"Traceback (most recent call last):...ValueError: Trying to change a constant value>>> constObj2 = const(" or not")>>> mutableObj = constObj.value + constObj2.value>>> mutableObj #just a string'Try to change me or not'
>>> from named_constants import Constants>>> class Colors(Constants):... black = 0... red = 1... white = 15...>>> c = Colors.black>>> c == 0True>>> cColors.black>>> c.name()'black'>>> Colors(0) is cTrue
# ---------- Constants.py ----------class Constants(object):"""Create objects with read-only (constant) attributes.Example:Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)print 10 + Nums.PIprint '----- Following line is deliberate ValueError -----'Nums.PI = 22"""
def __init__(self, *args, **kwargs):self._d = dict(*args, **kwargs)
def __iter__(self):return iter(self._d)
def __len__(self):return len(self._d)
# NOTE: This is only called if self lacks the attribute.# So it does not interfere with get of 'self._d', etc.def __getattr__(self, name):return self._d[name]
# ASSUMES '_..' attribute is OK to set. Need this to initialize 'self._d', etc.#If use as keys, they won't be constant.def __setattr__(self, name, value):if (name[0] == '_'):super(Constants, self).__setattr__(name, value)else:raise ValueError("setattr while locked", self)
if (__name__ == "__main__"):# Usage example.Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0)print 10 + Nums.PIprint '----- Following line is deliberate ValueError -----'Nums.PI = 22
import sys
PY_VER = sys.version.split()[0].split('.')
if int(PY_VER[0]) == 2:if int(PY_VER[1]) < 6:raise NotImplementedErrorelif int(PY_VER[1]) == 6:memoryview = buffer
class ConstArray(object):'''represent a constant bytearray'''def __init__(self, init):'''create a hidden bytearray and expose a memoryview of that bytearray forread-only use'''if int(PY_VER[1]) == 6:self.__array = bytearray(init.decode('hex'))else:self.__array = bytearray.fromhex(init)
self.array = memoryview(self.__array)
def __str__(self):return str(self.__array)
def __getitem__(self, *args, **kwargs):return self.array.__getitem__(*args, **kwargs)
ConstArray项赋值为TypeError:
>>> C1 = ConstArray('deadbeef')>>> C1[0] = 0Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: 'ConstArray' object does not support item assignment>>> C1[0]222
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 -> tupleset -> frozensetDict as value of a constant has no support.
>>> const = Constant()>>> del const.tempTraceback (most recent call last):NameError: name 'temp' is not defined>>> const.temp = 1>>> const.temp = 88Traceback (most recent call last):...TypeError: Constanst can not be changed>>> del const.tempTraceback (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.UNDEFINEDTraceback (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 doctestdoctest.testmod()
如果这还不够,请参阅完整的测试用例。
import decimalimport uuidimport datetimeimport 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 = 1self.assertEqual(self.const.CONSTANT, 1)self.const.Constant = 2self.assertEqual(self.const.Constant, 2)self.const.ConStAnT = 3self.assertEqual(self.const.ConStAnT, 3)self.const.constant = 4self.assertEqual(self.const.constant, 4)self.const.co_ns_ta_nt = 5self.assertEqual(self.const.co_ns_ta_nt, 5)self.const.constant1111 = 6self.assertEqual(self.const.constant1111, 6)
def test_create_and_change_integer_constant(self):
self.const.INT = 1234self.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 = .1234self.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 = Noneself.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 = Trueself.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 = valueself.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 = 1self.assertDictEqual(self.const(), {'A': 1})
self.const.B = "Text"self.assertDictEqual(self.const(), {'A': 1, 'B': "Text"})
from copy import copy
class Const(object):"A class to create objects with constant fields."
def __init__(self):object.__setattr__(self, '_names', [])
def add(self, **nameVals):for name, val in nameVals.iteritems():if hasattr(self, name):raise ConstError('A field with a name \'%s\' is already exist in Const class.' % name)
setattr(self, name, copy(val)) # set up getter
self._names.append(name)
return self
def __setattr__(self, name, val):if name in self._names:raise ConstError('You cannot change a value of a stored constant.')
object.__setattr__(self, name, val)
# metaclass of my class Fooclass FooMeta(type): pass
# class Fooclass Foo(metaclass=FooMeta): pass
# I create constants in my metaclassFooMeta.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
myVariable = 0CONST_daysInWeek = 7 # This is a constant - do not change its value.CONSTANT_daysInMonth = 30 # This is also a constant - do not change this value.
from enum import Enumclass StringConsts(str,Enum):ONE='one'TWO='two'
print(f'Truth is {StringConsts.ONE=="one"}') #Truth is TrueStringConsts.ONE="one" #Error: Cannot reassign
class MetaConstant(type):''' Metaclass that allows underlying class to store constants at class-level (subclass instance not needed).Non-existent attributes of underlying class (constants) can be set initially, but cannot be changed or deleted.'''
def __setattr__(klass, attr, value):'If attribute (constant) doesn\'t exist, set value. If attribute exists, raise AttributeError.'if hasattr(klass, attr):raise AttributeError(f'Can\'t change the value of the constant {klass.__name__}.{attr} to {value}'f' (the value of {klass.__name__}.{attr} is already set to'f' {getattr(klass, attr)}).')super().__setattr__(attr, value)
def __delattr__(klass, attr):if hasattr(klass, attr):raise AttributeError(f'Can\'t delete constant {klass.__name__}.{attr}'f' (set to {getattr(klass, attr)}).')
class Constants(metaclass=MetaConstant):'Container class for constants. No instantiation required.'#pass # uncomment if no constants set upon class creationB = 'Six' # sets Constants.B to 'Six'
Constants.B = 6 # AttributeErrordel Constants.B # AttributeError
Constants.A = 'Five' # sets Constants.A to 'Five'Constants.A = 5 # AttributeErrordel Constants.A # AttributeError
class ConstantError(Exception):pass # maybe give nice error message
class AllowConstants:_constants = None_class_constants = None
def __init__(self):self._constants = {}if self._class_constants is not None:self._constants.update(self._class_constants)
def constant(self, name, value):assert isinstance(name, str)assert self._constants is not None, "AllowConstants was not initialized"if name in self._constants or name in self.__dict__:raise ConstantError(name)self._constants[name] = value
def __getattr__(self, attr):if attr in self._constants:return self._constants[attr]raise AttributeError(attr)
def __setattr__(self, attr, val):if self._constants is None:# not finished initializationself.__dict__[attr] = valelse:if attr in self._constants:raise ConstantError(attr)else:self.__dict__[attr] = val
def __dir__(self):return super().__dir__() + list(self._constants.keys())
子类化此时,您创建的常量将受到保护:
class Example(AllowConstants):def __init__(self, a, b):super().__init__()self.constant("b", b)self.a = a
def try_a(self, value):self.a = value
def try_b(self, value):self.b = value
def __str__(self):return str({"a": self.a, "b": self.b})
def __repr__(self):return self.__str__()
example = Example(1, 2)print(example) # {'a': 1, 'b': 2}
example.try_a(5)print(example) # {'a': 5, 'b': 2}
example.try_b(6) # ConstantError: b
example.a = 7print(example) # {'a': 7, 'b': 2}
example.b = 8 # ConstantError: b
print(hasattr(example, "b")) # True
# To show that constants really do immediately become constant:
class AnotherExample(AllowConstants):def __init__(self):super().__init__()self.constant("a", 2)print(self.a)self.a=3
AnotherExample() # 2 ConstantError: a
# finally, for class constants:class YetAnotherExample(Example):_class_constants = {'BLA': 3}
def __init__(self, a, b):super().__init__(a,b)
def try_BLA(self, value):self.BLA = value
ex3 = YetAnotherExample(10, 20)ex3.BLA # 3ex3.try_BLA(10) # ConstantError: BLAex3.BLA = 4 # ConstantError: BLA
class AllowConstants:_constants = None_class_constants = None
def __init__(self):self._constants = {}self._update_class_constants()
def __init_subclass__(cls):"""Without this, it is necessary to set _class_constants in any subclass of any class that has class constants"""if cls._class_constants is not None:#prevent trouble where _class_constants is not overwrittenpossible_cases = cls.__mro__[1:-1] #0 will have cls and -1 will have objectfor case in possible_cases:if cls._class_constants is case._class_constants:cls._class_constants = Nonebreak
def _update_class_constants(self):"""Help with the inheritance of class constants"""for superclass in self.__class__.__mro__:if hasattr(superclass, "_class_constants"):sccc = superclass._class_constantsif sccc is not None:for key in sccc:if key in self._constants:raise ConstantError(key)self._constants.update(sccc)
def constant(self, name, value):assert isinstance(name, str)assert self._constants is not None, "AllowConstants was not initialized"if name in self._constants or name in self.__dict__:raise ConstantError(name)self._constants[name] = value
def __getattr__(self, attr):if attr in self._constants:return self._constants[attr]raise AttributeError(attr)
def __setattr__(self, attr, val):if self._constants is None:# not finished initializationself.__dict__[attr] = valelse:if attr in self._constants:raise ConstantError(attr)else:self.__dict__[attr] = val
def __dir__(self):return super().__dir__() + list(self._constants.keys())
这样你就可以做:
class Example(AllowConstants):_class_constants = {"BLA": 2}def __init__(self, a, b):super().__init__()self.constant("b", b)self.a = a
def try_a(self, value):self.a = value
def try_b(self, value):self.b = value
def __str__(self):return str({"a": self.a, "b": self.b})
def __repr__(self):return self.__str__()
class ChildExample1(Example):_class_constants = {"BLI": 88}
class ChildExample2(Example):_class_constants = {"BLA": 44}
example = ChildExample1(2,3)print(example.BLA) # 2example.BLA = 8 # ConstantError BLAprint(example.BLI) # 88example.BLI = 8 # ConstantError BLI
example = ChildExample2(2,3) # ConstantError BLA
NAMES = 'KING QUEEN ROOK BISHOP KNIGHT PAWN'xs = NAMES.split()
Pieces = constants('Pieces', NAMES) # All of these do the same thing.Pieces = constants('Pieces', xs)Pieces = constants('Pieces', tuple(xs))
>>> import sys>>> import ast>>> import dis>>> import types>>>>>>>>> def hook(name, tup):... if name == "exec" and tup:... if tup and isinstance(tup[0], types.CodeType):... code = tup[0]... store_instruction_arg = None... instructions = [dis.opname[op] for op in code.co_code]...... for i, instruction in enumerate(instructions):... if instruction == "STORE_NAME":... store_instruction_arg = code.co_code[i + 1]... break...... if store_instruction_arg is not None:... var_name = code.co_names[store_instruction_arg]... if var_name in globals():... raise Exception("Cannot re-assign variable")...>>>>>> sys.addaudithook(hook)>>>>>> a = '123'>>> a = 456Traceback (most recent call last):File "<stdin>", line 16, in hookException: Cannot re-assign variable>>>>>> a'123'
from dataclasses import dataclass
@dataclass(frozen=True)class _Const:SOME_STRING = 'some_string'SOME_INT = 5
Const = _Const()
# In another file import Const and tryprint(Const.SOME_STRING) # ITS OK!Const.SOME_INT = 6 # dataclasses.FrozenInstanceError: cannot assign to field 'SOME_INT'