How do I create a constant in Python?

后端 未结 30 2653
既然无缘
既然无缘 2020-11-22 09:07

Is there a way to declare a constant in Python? In Java we can create constant values in this manner:

public static          


        
相关标签:
30条回答
  • 2020-11-22 09:38

    A tuple technically qualifies as a constant, as a tuple will raise an error if you try to change one of its values. If you want to declare a tuple with one value, then place a comma after its only value, like this:

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

    To check this variable's value, use something similar to this:

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

    If you attempt to change this value, an error will be raised.

    0 讨论(0)
  • 2020-11-22 09:41

    Here it is a collection of idioms that I created as an attempt to improve some of the already available answers.

    I know the use of constant is not pythonic, and you should not do this at home!

    However, Python is such a dynamic language! This forum shows how it is possible the creation of constructs that looks and feels like constants. This answer has as the primary purpose to explore what can be expressed by the language.

    Please do not be too harsh with me :-).

    For more details I wrote a accompaniment blog about these idioms.

    In this post, I will call a constant variable to a constant reference to values (immutable or otherwise). Moreover, I say that a variable has a frozen value when it references a mutable object that a client-code cannot update its value(s).

    A space of constants (SpaceConstants)

    This idiom creates what looks like a namespace of constant variables (a.k.a. SpaceConstants). It is a modification of a code snippet by Alex Martelli to avoid the use of module objects. In particular, this modification uses what I call a class factory because within SpaceConstants function, a class called SpaceConstants is defined, and an instance of it is returned.

    I explored the use of class factory to implement a policy-based design look-alike in Python in stackoverflow and also in a blogpost.

    def SpaceConstants():
        def setattr(self, name, value):
            if hasattr(self, name):
                raise AttributeError(
                    "Cannot reassign members"
                )
            self.__dict__[name] = value
        cls = type('SpaceConstants', (), {
            '__setattr__': setattr
        })
        return cls()
    
    sc = SpaceConstants()
    
    print(sc.x) # raise "AttributeError: 'SpaceConstants' object has no attribute 'x'"
    sc.x = 2 # bind attribute x
    print(sc.x) # print "2"
    sc.x = 3 # raise "AttributeError: Cannot reassign members"
    sc.y = {'name': 'y', 'value': 2} # bind attribute y
    print(sc.y) # print "{'name': 'y', 'value': 2}"
    sc.y['name'] = 'yprime' # mutable object can be changed
    print(sc.y) # print "{'name': 'yprime', 'value': 2}"
    sc.y = {} # raise "AttributeError: Cannot reassign members"
    
    

    A space of frozen values (SpaceFrozenValues)

    This next idiom is a modification of the SpaceConstants in where referenced mutable objects are frozen. This implementation exploits what I call shared closure between setattr and getattr functions. The value of the mutable object is copied and referenced by variable cache define inside of the function shared closure. It forms what I call a closure protected copy of a mutable object.

    You must be careful in using this idiom because getattr return the value of cache by doing a deep copy. This operation could have a significant performance impact on large objects!

    from copy import deepcopy
    
    def SpaceFrozenValues():
        cache = {}
        def setattr(self, name, value):
            nonlocal cache
            if name in cache:
                raise AttributeError(
                    "Cannot reassign members"
                )
            cache[name] = deepcopy(value)
        def getattr(self, name):
            nonlocal cache
            if name not in cache:
                raise AttributeError(
                    "Object has no attribute '{}'".format(name)
                )
            return deepcopy(cache[name])
        cls = type('SpaceFrozenValues', (),{
            '__getattr__': getattr,
            '__setattr__': setattr
        })
        return cls()
    
    fv = SpaceFrozenValues()
    print(fv.x) # AttributeError: Object has no attribute 'x'
    fv.x = 2 # bind attribute x
    print(fv.x) # print "2"
    fv.x = 3 # raise "AttributeError: Cannot reassign members"
    fv.y = {'name': 'y', 'value': 2} # bind attribute y
    print(fv.y) # print "{'name': 'y', 'value': 2}"
    fv.y['name'] = 'yprime' # you can try to change mutable objects
    print(fv.y) # print "{'name': 'y', 'value': 2}"
    fv.y = {} # raise "AttributeError: Cannot reassign members"
    

    A constant space (ConstantSpace)

    This idiom is an immutable namespace of constant variables or ConstantSpace. It is a combination of awesomely simple Jon Betts' answer in stackoverflow with a class factory.

    def ConstantSpace(**args):
        args['__slots__'] = ()
        cls = type('ConstantSpace', (), args)
        return cls()
    
    cs = ConstantSpace(
        x = 2,
        y = {'name': 'y', 'value': 2}
    )
    
    print(cs.x) # print "2"
    cs.x = 3 # raise "AttributeError: 'ConstantSpace' object attribute 'x' is read-only"
    print(cs.y) # print "{'name': 'y', 'value': 2}"
    cs.y['name'] = 'yprime' # mutable object can be changed
    print(cs.y) # print "{'name': 'yprime', 'value': 2}"
    cs.y = {} # raise "AttributeError: 'ConstantSpace' object attribute 'x' is read-only"
    cs.z = 3 # raise "AttributeError: 'ConstantSpace' object has no attribute 'z'"
    

    A frozen space (FrozenSpace)

    This idiom is an immutable namespace of frozen variables or FrozenSpace. It is derived from the previous pattern by making each variable a protected property by closure of the generated FrozenSpace class.

    from copy import deepcopy
    
    def FreezeProperty(value):
        cache = deepcopy(value)
        return property(
            lambda self: deepcopy(cache)
        )
    
    def FrozenSpace(**args):
        args = {k: FreezeProperty(v) for k, v in args.items()}
        args['__slots__'] = ()
        cls = type('FrozenSpace', (), args)
        return cls()
    
    fs = FrozenSpace(
        x = 2,
        y = {'name': 'y', 'value': 2}
    )
    
    print(fs.x) # print "2"
    fs.x = 3 # raise "AttributeError: 'FrozenSpace' object attribute 'x' is read-only"
    print(fs.y) # print "{'name': 'y', 'value': 2}"
    fs.y['name'] = 'yprime' # try to change mutable object
    print(fs.y) # print "{'name': 'y', 'value': 2}"
    fs.y = {} # raise "AttributeError: 'FrozenSpace' object attribute 'x' is read-only"
    fs.z = 3 # raise "AttributeError: 'FrozenSpace' object has no attribute 'z'"
    
    0 讨论(0)
  • 2020-11-22 09:42

    You can use a namedtuple as a workaround to effectively create a constant that works the same way as a static final variable in Java (a Java "constant"). As workarounds go, it's sort of elegant. (A more elegant approach would be to simply improve the Python language --- what sort of language lets you redefine math.pi? -- but I digress.)

    (As I write this, I realize another answer to this question mentioned namedtuple, but I'll continue here because I'll show a syntax that more closely parallels what you'd expect in Java, as there is no need to create a named type as namedtuple forces you to do.)

    Following your example, you'll remember that in Java we must define the constant inside some class; because you didn't mention a class name, let's call it Foo. Here's the Java class:

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

    Here's the equivalent Python.

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

    The key point I want to add here is that you don't need a separate Foo type (an "anonymous named tuple" would be nice, even though that sounds like an oxymoron), so we name our namedtuple _Foo so that hopefully it won't escape to importing modules.

    The second point here is that we immediately create an instance of the nametuple, calling it Foo; there's no need to do this in a separate step (unless you want to). Now you can do what you can do in Java:

    >>> Foo.CONST_NAME
    'Name'
    

    But you can't assign to it:

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

    Acknowledgement: I thought I invented the namedtuple approach, but then I see that someone else gave a similar (although less compact) answer. Then I also noticed What are "named tuples" in Python?, which points out that sys.version_info is now a namedtuple, so perhaps the Python standard library already came up with this idea much earlier.

    Note that unfortunately (this still being Python), you can erase the entire Foo assignment altogether:

    >>> Foo = 'bar'
    

    (facepalm)

    But at least we're preventing the Foo.CONST_NAME value from being changed, and that's better than nothing. Good luck.

    0 讨论(0)
  • 2020-11-22 09:44

    I've recently found a very succinct update to this which automatically raises meaningful error messages and prevents access via __dict__:

    class CONST(object):
        __slots__ = ()
        FOO = 1234
    
    CONST = CONST()
    
    # ----------
    
    print(CONST.FOO)    # 1234
    
    CONST.FOO = 4321              # AttributeError: 'CONST' object attribute 'FOO' is read-only
    CONST.__dict__['FOO'] = 4321  # AttributeError: 'CONST' object has no attribute '__dict__'
    CONST.BAR = 5678              # AttributeError: 'CONST' object has no attribute 'BAR'
    

    We define over ourselves as to make ourselves an instance and then use slots to ensure that no additional attributes can be added. This also removes the __dict__ access route. Of course, the whole object can still be redefined.

    Edit - Original solution

    I'm probably missing a trick here, but this seems to work for me:

    class CONST(object):
        FOO = 1234
    
        def __setattr__(self, *_):
            pass
    
    CONST = CONST()
    
    #----------
    
    print CONST.FOO    # 1234
    
    CONST.FOO = 4321
    CONST.BAR = 5678
    
    print CONST.FOO    # Still 1234!
    print CONST.BAR    # Oops AttributeError
    

    Creating the instance allows the magic __setattr__ method to kick in and intercept attempts to set the FOO variable. You could throw an exception here if you wanted to. Instantiating the instance over the class name prevents access directly via the class.

    It's a total pain for one value, but you could attach lots to your CONST object. Having an upper class, class name also seems a bit grotty, but I think it's quite succinct overall.

    0 讨论(0)
  • 2020-11-22 09:44

    I would make a class that overrides the __setattr__ method of the base object class and wrap my constants with that, note that I'm using python 2.7:

    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)
    

    To wrap a string:

    >>> 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'
    

    It's pretty simple, but if you want to use your constants the same as you would a non-constant object (without using constObj.value), it will be a bit more intensive. It's possible that this could cause problems, so it might be best to keep the .value to show and know that you are doing operations with constants (maybe not the most 'pythonic' way though).

    0 讨论(0)
  • 2020-11-22 09:46

    PEP 591 has the 'final' qualifier. Enforcement is down to the type checker.

    So you can do:

    MY_CONSTANT: Final = 12407
    

    Note: Final keyword is only applicable for Python 3.8 version

    0 讨论(0)
提交回复
热议问题