Comment créer une constante en Python?

992

Existe-t-il un moyen de déclarer une constante en Python? En Java, nous pouvons créer des valeurs constantes de cette manière:

public static final String CONST_NAME = "Name";

Quel est l'équivalent de la déclaration constante Java ci-dessus en Python?

zfranciscus
la source
6
en fait, la façon de créer des variables en lecture seule est possible via la fonction de propriété / décorateur de python . la réponse de inv est un exemple d'utilisation personnalisée de cela. La propriété est plus générale que cela, cependant, une bonne analyse de son fonctionnement est sur les attributs et les méthodes Python de Shalabh Chaturvedi .
n611x007
20
À mon humble avis, l'application de la constance n'est "pas pythonique". Dans Python 2.7, vous pouvez même écrire True=False, puis (2+2==4)==Truerevenir False.
osa
8
Comme d'autres réponses le suggèrent, il n'y a aucun moyen ou aucun besoin de déclarer des constantes. Mais vous pouvez lire ce PEP sur les conventions. par exemple THIS_IS_A_CONSTANT
Rasika Perera
34
@osa: Vous ne pouvez pas faire ça en python 3 - SyntaxError: can't assign to keyword. Cela semble être une bonne chose.
naught101
3
Surpris, cela n'a pas été mentionné jusqu'à présent, mais Enums semble être un bon moyen de définir des constantes énumérées.
cs95

Réponses:

973

Non, il n'y en a pas. Vous ne pouvez pas déclarer une variable ou une valeur comme constante en Python. Ne le changez pas.

Si vous êtes dans une classe, l'équivalent serait:

class Foo(object):
    CONST_NAME = "Name"

sinon, c'est juste

CONST_NAME = "Name"

Mais vous voudrez peut-être jeter un œil aux constantes d' extrait de code en Python d'Alex Martelli.


Depuis Python 3.8, il y a une typing.Finalannotation de variable qui indiquera aux vérificateurs de type statique (comme mypy) que votre variable ne doit pas être réaffectée. C'est l'équivalent le plus proche de Java final. Cependant, cela n'empêche pas réellement la réaffectation :

from typing import Final

a: Final = 1

# Executes fine, but mypy will report an error if you run mypy on this:
a = 2
Felix Kling
la source
27
Faites plutôt ce qui se trouve dans "Constantes en Python", vous devez utiliser la fonction "propriété" ou le décorateur.
Seth Johnson
26
Les gens posent des questions sur la même fonctionnalité dans Perl. Il existe un module d'importation appelé "use constant", mais (AFAIK) est-ce juste un wrapper pour créer une petite fonction qui retourne la valeur. Je fais de même en Python. Exemple:def MY_CONST_VALUE(): return 123
kevinarpe
8
"Non, il n'y en a pas." Certes, mais en s'appuyant sur le travail d'autres personnes, j'ai ajouté une réponse, bien en dessous, avec une implémentation courte et simple de "Constantes" pour python 2.7 (qui manque "enum"). Celles-ci sont en lecture seule name.attributeet peuvent contenir n'importe quelle valeur. La déclaration est facile Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0), l'utilisation est simple print 10 + Nums.PI, essayez de changer les résultats en exception Nums.PI = 22=> ValueError (..).
ToolmakerSteve
110
Ne le changez pas. tu as fait ma journée
Hi-Angel
89
"Ne changez rien" n'est pas du tout utile. Il ne répond pas à la question et je suggère qu'il soit supprimé.
Bartek Banachewicz
354

Il n'y a pas de constmot-clé comme dans d'autres langues, mais il est possible de créer une propriété qui a une "fonction getter" pour lire les données, mais pas de "fonction setter" pour réécrire les données. Cela protège essentiellement l'identifiant contre toute modification.

Voici une implémentation alternative utilisant la propriété de classe:

Notez que le code est loin d'être facile pour un lecteur qui s'interroge sur les constantes. Voir l'explication ci-dessous

def constant(f):
    def fset(self, value):
        raise TypeError
    def fget(self):
        return f()
    return property(fget, fset)

class _Const(object):
    @constant
    def FOO():
        return 0xBAADFACE
    @constant
    def BAR():
        return 0xDEADBEEF

CONST = _Const()

print CONST.FOO
##3131964110

CONST.FOO = 0
##Traceback (most recent call last):
##    ...
##    CONST.FOO = 0
##TypeError: None

Explication du code:

  1. Définissez une fonction constantqui prend une expression et l'utilise pour construire un "getter" - une fonction qui renvoie uniquement la valeur de l'expression.
  2. La fonction setter déclenche une TypeError donc elle est en lecture seule
  3. Utilisez la constantfonction que nous venons de créer comme décoration pour définir rapidement des propriétés en lecture seule.

Et d'une autre manière plus ancienne:

(Le code est assez délicat, plus d'explications ci-dessous)

class _Const(object):
    @apply
    def FOO():
        def fset(self, value):
            raise TypeError
        def fget(self):
            return 0xBAADFACE
        return property(**locals())

CONST = _Const()

print CONST.FOO
##3131964110

CONST.FOO = 0
##Traceback (most recent call last):
##    ...
##    CONST.FOO = 0
##TypeError: None

Notez que le décorateur @apply semble être obsolète.

  1. Pour définir l'identifiant FOO, les premiers définissent deux fonctions (fset, fget - les noms sont à mon choix).
  2. Utilisez ensuite la propertyfonction intégrée pour construire un objet qui peut être "défini" ou "récupéré".
  3. Notez que les propertydeux premiers paramètres de la fonction sont nommés fsetet fget.
  4. Utilisez le fait que nous avons choisi ces mêmes noms pour notre propre getter et setter et créer un dictionnaire de mots clés en utilisant le ** (double astérisque) appliqué à toutes les définitions locales de cette portée pour passer des paramètres à la propertyfonction
inv
la source
11
Sur la base de la documentation sur AttributeErroret TypeError, je pense que l'exception levée devrait être une nouvelle erreur, que je propose de nommer ConstantErrorou quelque chose comme ça, qui est une sous-classe de TypeError. La section dans les documents qui me fait penser que: docs.python.org/2/library/exceptions.html
ArtOfWarfare
3
Je suis surpris de ce code. Pourquoi les méthodes FOO () et BAR () font-elles avoir self comme argument? Mon IDE souligne les crochets en rouge (erreur "compiler"). Je suis fatigué de me mettre dedans mais je reçois une erreur.
user3770060
10
Aller à ces longueurs met en évidence une lacune claire dans le langage python. Pourquoi n'ont-ils pas ressenti le besoin d'ajouter ceci à Python 3. Je ne peux pas croire que personne ne l'ait suggéré et je ne vois tout simplement pas la logique derrière un comité qui va 'nah, constantes? non.
Andrew S
8
Et votre solution peut toujours être modifiée par un programmeur python déterminé en utilisantCONST.__dict__['FOO'] = 7
pppery
11
@OscarSmith, je pense que cela améliorerait la conception du «code auto-documenté». Lorsque je précise dans le code qu'une certaine valeur ne peut pas changer, il est plus facile à comprendre que de lire tout le code source et de réaliser qu'une certaine valeur ne change jamais. En outre, cela bloque la possibilité que quelqu'un modifie une valeur qui devrait être, eh bien, constante. N'oubliez pas: explicite vaut mieux qu'implicite.
Gabriel
112

En Python, au lieu d'appliquer un langage, les gens utilisent des conventions de dénomination, par exemple __methodpour les méthodes privées et _methodpour les méthodes protégées.

Donc, de la même manière, vous pouvez simplement déclarer la constante comme toutes les majuscules, par exemple

MY_CONSTANT = "one"

Si vous voulez que cette constante ne change jamais, vous pouvez vous connecter à l'accès aux attributs et faire des tours, mais une approche plus simple consiste à déclarer une fonction

def MY_CONSTANT():
    return "one"

Le seul problème est partout où vous devrez faire MY_CONSTANT (), mais encore une fois MY_CONSTANT = "one" c'est la bonne façon en python (généralement).

Vous pouvez également utiliser namedtuple pour créer des constantes:

>>> from collections import namedtuple
>>> Constants = namedtuple('Constants', ['pi', 'e'])
>>> constants = Constants(3.14, 2.718)
>>> constants.pi
3.14
>>> constants.pi = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Anurag Uniyal
la source
18
Faire def MY_CONSTANT(): return "one"n'arrêtera pas quelqu'un, plus tard dans le code, faisant MY_CONSTANT = "two"(ou redéclarant la fonction).
Matthew Schinckel
6
@MatthewSchinckel, il s'agit de convention, également changer MY_CONSTANT ne changera pas l'utilisation MY_CONSTANT () mais générera une erreur, et en python si vous voulez vous pouvez changer quoi que ce soit, aucune astuce intelligente ne peut vous protéger.
Anurag Uniyal
3
Merci d'avoir évoqué l'approche nommée tuple. Certainement innovant. Vous pouvez également trouver mon "commentaire" ici pertinent.
RayLuo
@MatthewSchinckel vous pouvez redéfinir TOUT en python, donc ce n'est pas vraiment un bon point.
cslotty
@MatthewSchinckel L'idée n'est pas d'écrire MY_CONSTANT = MY_CONSTANT(), mais d'utiliser MY_CONSTANT()comme constante. Bien sûr, ça. Mais cela va bien et est tout à fait conforme au principe python "Nous sommes tous des adultes ici" - c'est-à-dire qu'il sera rarement interdit au développeur de décider de remplacer une règle lorsqu'il a de bonnes raisons et sait ce qu'il fait.
jonathan.scholbach
69

J'ai récemment trouvé une mise à jour très succincte qui soulève automatiquement des messages d'erreur significatifs et empêche l'accès 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'

Nous définissons sur nous-mêmes de nous faire une instance, puis utilisons des emplacements pour nous assurer qu'aucun attribut supplémentaire ne peut être ajouté. Cela supprime également la__dict__ route d'accès. Bien sûr, tout l'objet peut encore être redéfini.

Edit - Solution originale

Il me manque probablement une astuce ici, mais cela semble fonctionner pour moi:

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

La création de l'instance permet à la __setattr__méthode magique de démarrer et d'intercepter les tentatives de définition de la FOOvariable. Vous pouvez lancer une exception ici si vous le souhaitez. L'instanciation de l'instance sur le nom de la classe empêche l'accès directement via la classe.

C'est une douleur totale pour une valeur, mais vous pouvez attacher beaucoup à votre CONSTobjet. Ayant une classe supérieure, le nom de classe semble aussi un peu grincheux, mais je pense que c'est assez succinct dans l'ensemble.

Jon Betts
la source
11
C'est la réponse la meilleure et la plus claire, car elle a le moins de «mécanisme», mais le plus de fonctionnalités. Il est important de lever une exception ... pas une option.
Erik Aronesty
J'ai élaboré un itinéraire plus court qui produit automatiquement des erreurs significatives mais qui est à peu près du même style. J'ai laissé l'idée originale ici pour comparaison.
Jon Betts
Quel dommage que vous ayez encore besoin de ce CONST.préfixe. Dans les situations multi-modules, cela sera également compliqué.
Alfe
1
Je pense que vous voudriez généralement regrouper les constantes dans des faisceaux connexes dans cette situation (plutôt que d'avoir un objet CONST géant), donc ce n'est probablement pas une si mauvaise chose.
Jon Betts
Pourquoi cette réponse est-elle si loin?! La __slots__solution est tellement élégante et efficace. D'après tout ce que j'ai lu, c'est à peu près aussi proche que possible de la création de constantes en Python. Merci beaucoup. Et pour toutes les personnes intéressées, voici une explication brillante et approfondie de la __slots__magie.
JohnGalt
34

Python n'a pas de constantes.

Peut-être que l'alternative la plus simple est de lui définir une fonction:

def MY_CONSTANT():
    return 42

MY_CONSTANT() a maintenant toutes les fonctionnalités d'une constante (plus quelques accolades ennuyeuses).

Saeed Baig
la source
1
Je voulais juste ajouter cette suggestion, mais heureusement, je suis descendu jusqu'aux réponses les moins bien notées. J'espère qu'il sera encore plus voté et je suis entièrement d'accord qu'il a toutes les fonctionnalités d'une constante et qu'il est très simple et direct. En regardant la quantité de code passe-partout dans toutes les solutions sophistiquées, je trouve les accolades relativement ennuyeuses.
yaccob
1
il s'agit de la réponse la plus simple, bien qu'il convient de noter qu'elle a des frais généraux et n'arrêtera pas les idiots de modifier la valeur de retour. Cela empêchera simplement le code de changer la source plus
loin
@MrMesees modifier la valeur de retour? Voulez-vous dire éditer la source? Mais de cela, vous n'êtes pas protégé même en C ++, où les constantes (comme constexpr) sont de vraies constantes dures.
Ruslan
@Ruslan, ce que je voulais dire, c'est que comme python n'a pas de constexpr, cela n'arrêterait pas la valeur en cours de modification après son retour dans un contexte externe. Rien n'a été fait pour 42 pour appliquer l'état gelé dans cet exemple.
MrMesees
20

En plus des deux meilleures réponses (utilisez simplement des variables avec des noms UPPERCASE ou utilisez des propriétés pour rendre les valeurs en lecture seule), je tiens à mentionner qu'il est possible d'utiliser des métaclasses afin d'implémenter des constantes nommées . Je fournis une solution très simple en utilisant des métaclasses sur GitHub qui peut être utile si vous voulez que les valeurs soient plus informatives sur leur type / nom:

>>> from named_constants import Constants
>>> class Colors(Constants):
...     black = 0
...     red = 1
...     white = 15
...
>>> c = Colors.black
>>> c == 0
True
>>> c
Colors.black
>>> c.name()
'black'
>>> Colors(0) is c
True

Il s'agit de Python légèrement plus avancé, mais toujours très facile à utiliser et pratique. (Le module a quelques fonctionnalités supplémentaires, y compris les constantes en lecture seule, voir son fichier README.)

Il existe des solutions similaires flottant dans divers référentiels, mais à ma connaissance, il leur manque soit l'une des fonctionnalités fondamentales que j'attendrais des constantes (comme être constante ou être de type arbitraire), soit elles ont des fonctionnalités ésotériques ajoutées qui les rendre moins généralement applicables. Mais YMMV, je serais reconnaissant pour vos commentaires. :-)

hans_meine
la source
3
J'aime votre implémentation sur GitHub. J'étais presque prêt à écrire une classe de base qui implémentait la fonctionnalité de recherche inversée, mais je vois que vous l'avez fait et plus encore!
Kerr
Merci, @Kerr, c'est le premier feedback que j'ai reçu et m'a fait plaisir. :-)
hans_meine
Impressionnant. Je viens de l'essayer. C'est bien d'avoir cela en option. Bien que je n'aie pas décidé si je me soucie assez de l'aspect en lecture seule, utiliser cela plutôt que de simplement le faire def enum(**enums): return type('Enum', (), enums). Numbers = enum(ONE=1, TWO=2, THREE='three'), selon stackoverflow.com/a/1695250/199364 , section "Dans les versions antérieures ..."
ToolmakerSteve
19

Les propriétés sont un moyen de créer des constantes. Vous pouvez le faire en déclarant une propriété getter, mais en ignorant le setter. Par exemple:

class MyFinalProperty(object):

    @property
    def name(self):
        return "John"

Vous pouvez consulter un article que j'ai écrit pour trouver plus de façons d'utiliser les propriétés Python.

nvd
la source
Solution sous-estimée. Je viens de l'implémenter après avoir trouvé cette page (pas cette réponse) et j'ai encerclé pour l'ajouter si ce n'est déjà fait. Je voulais souligner l'utilité de cette réponse.
Marc
18

Edit: Ajout d'un exemple de code pour Python 3

Remarque: cette autre réponse semble fournir une implémentation beaucoup plus complète similaire à la suivante (avec plus de fonctionnalités).

Commencez par créer une métaclasse :

class MetaConst(type):
    def __getattr__(cls, key):
        return cls[key]

    def __setattr__(cls, key, value):
        raise TypeError

Cela empêche les propriétés statiques d'être modifiées. Créez ensuite une autre classe qui utilise cette métaclasse:

class Const(object):
    __metaclass__ = MetaConst

    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        raise TypeError

Ou, si vous utilisez Python 3:

class Const(object, metaclass=MetaConst):
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        raise TypeError

Cela devrait empêcher la modification des accessoires d'instance. Pour l'utiliser, héritez:

class MyConst(Const):
    A = 1
    B = 2

Maintenant, les accessoires, accessibles directement ou via une instance, doivent être constants:

MyConst.A
# 1
my_const = MyConst()
my_const.A
# 1

MyConst.A = 'changed'
# TypeError
my_const.A = 'changed'
# TypeError

Voici un exemple de ci-dessus en action. Voici un autre exemple pour Python 3.

double fil
la source
10

Vous pouvez utiliser un tuple nommé comme solution de contournement pour créer efficacement une constante qui fonctionne de la même manière qu'une variable finale statique en Java (une "constante" Java). Comme solutions de contournement, c'est un peu élégant. (Une approche plus élégante consisterait simplement à améliorer le langage Python --- quel type de langage vous permet de redéfinir math.pi? - mais je m'égare.)

(Au moment où j'écris ceci, je réalise une autre réponse à cette question mentionnée namedtuple, mais je vais continuer ici parce que je vais montrer une syntaxe qui correspond plus étroitement à ce que vous attendez en Java, car il n'est pas nécessaire de créer un nommé tapez comme nommétuple vous oblige à le faire.)

En suivant votre exemple, vous vous souviendrez qu'en Java, nous devons définir la constante à l' intérieur d'une classe ; parce que vous n'avez pas mentionné de nom de classe, appelons-le Foo. Voici la classe Java:

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

Voici l'équivalent Python.

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

Le point clé que je veux ajouter ici est que vous n'avez pas besoin d'un Footype séparé (un "tuple nommé anonyme" serait bien, même si cela ressemble à un oxymore), donc nous nommons notre nommé tuple _Foopour que, espérons-le, il ne le soit pas échapper à l'importation de modules.

Le deuxième point ici est que nous créons immédiatement une instance du nametuple, en l'appelant Foo; il n'est pas nécessaire de le faire dans une étape distincte (sauf si vous le souhaitez). Vous pouvez maintenant faire ce que vous pouvez faire en Java:

>>> Foo.CONST_NAME
'Name'

Mais vous ne pouvez pas lui attribuer:

>>> Foo.CONST_NAME = 'bar'

AttributeError: can't set attribute

Remerciements: je pensais avoir inventé l'approche nommée tuple, mais je vois que quelqu'un d'autre a donné une réponse similaire (bien que moins compacte). Ensuite, j'ai également remarqué Que sont les "tuples nommés" en Python? , qui souligne qu'il sys.version_infos'agit désormais d'un tuple nommé, donc peut-être que la bibliothèque standard Python a déjà proposé cette idée beaucoup plus tôt.

Notez que malheureusement (ceci étant toujours Python), vous pouvez effacer la totalité de l' Fooaffectation:

>>> Foo = 'bar'

(facepalm)

Mais au moins, nous empêchons la Foo.CONST_NAMEvaleur d'être modifiée, et c'est mieux que rien. Bonne chance.

Garret Wilson
la source
Merci d'avoir évoqué l'approche nommée tuple. Certainement innovant. Vous pouvez également trouver mon "commentaire" ici pertinent.
RayLuo
10

PEP 591 a le qualificatif «final». L'application est au vérificateur de type.

Vous pouvez donc faire:

MY_CONSTANT: Final = 12407

Remarque: le Final mot-clé s'applique uniquement à la version Python 3.8

Mike Amy
la source
9

Voici une implémentation d'une classe "Constantes", qui crée des instances avec des attributs en lecture seule (constants). Par exemple, vous pouvez utiliser Nums.PIpour obtenir une valeur qui a été initialisée en tant que 3.14159, et Nums.PI = 22déclenche une exception.

# ---------- 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.PI
        print '----- 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.PI
    print '----- Following line is deliberate ValueError -----'
    Nums.PI = 22

Merci à FrozenDict de @MikeGraham , que j'ai utilisé comme point de départ. Modifié, donc au lieu de Nums['ONE']la syntaxe d'utilisation est Nums.ONE.

Et grâce à la réponse de @ Raufio, pour l'idée de remplacer __ setattr __.

Ou pour une implémentation avec plus de fonctionnalités, consultez les nommé_constants de @Hans_meine sur GitHub

ToolmakerSteve
la source
2
Python est un langage d'adultes consentants. Il n'y a aucune protection contre quelque chose comme ça. Nums._d['PI'] = 22 Le langage lui-même ne fournit aucun moyen de marquer les choses comme non mutables, je crois.
Ajay M
8

Un tuple est techniquement considéré comme une constante, car un tuple génère une erreur si vous essayez de modifier l'une de ses valeurs. Si vous souhaitez déclarer un tuple avec une valeur, placez une virgule après sa seule valeur, comme ceci:

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

Pour vérifier la valeur de cette variable, utilisez quelque chose de similaire à ceci:

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

Si vous essayez de modifier cette valeur, une erreur sera générée.

tobahhh
la source
7

Je voudrais créer une classe qui remplace la __setattr__méthode de la classe d'objet de base et envelopper mes constantes avec cela, notez que j'utilise 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)

Pour encapsuler une chaîne:

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

C'est assez simple, mais si vous souhaitez utiliser vos constantes de la même manière qu'un objet non constant (sans utiliser constObj.value), ce sera un peu plus intensif. Il est possible que cela cause des problèmes, il est donc préférable de garder le .valuepour afficher et de savoir que vous effectuez des opérations avec des constantes (peut-être pas la manière la plus «pythonique»).

Raufio
la source
+1 pour une approche intéressante. Mais pas aussi net que les réponses qui avaient déjà été fournies. Et même la solution la plus simple suggérée précédemment def ONE(): return 1est plus facile à utiliser ONE()que cette réponse ONE.value.
ToolmakerSteve
7

Malheureusement, le Python n'a pas encore de constantes et c'est dommage. ES6 a déjà ajouté des constantes de prise en charge à JavaScript ( https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const ) car c'est une chose très utile dans n'importe quel langage de programmation. Comme indiqué dans d'autres réponses de la communauté Python, utilisez la variable majuscule convention - utilisateur comme constantes, mais elle ne protège pas contre les erreurs arbitraires dans le code. Si vous le souhaitez, une solution à fichier unique peut vous être utile dans la suite (voir docstrings comment l'utiliser).

fichier 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()

Si cela ne suffit pas, consultez le test complet pour cela.

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"})

Avantages: 1. Accès à toutes les constantes pour l'ensemble du projet 2. Contrôle strict des valeurs des constantes

Manque: 1. Pas de support pour les types personnalisés et le type «dict»

Remarques:

  1. Testé avec Python3.4 et Python3.5 (j'utilise le 'tox' pour cela)

  2. Environnement de test:

.

$ 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
PADYMKO
la source
Vous pouvez améliorer légèrement cela en convertissant automatiquement les dictionnaires en tuples nommés
Peter Schorn
6

La manière pythonique de déclarer des "constantes" est fondamentalement une variable de niveau module:

RED = 1
GREEN = 2
BLUE = 3

Et puis écrivez vos classes ou fonctions. Étant donné que les constantes sont presque toujours des entiers et qu'elles sont également immuables en Python, vous avez très peu de chances de le modifier.

Sauf, bien sûr, si vous définissez explicitement RED = 2.

Xavier Ho
la source
21
Oui, mais bloquer la possibilité de "définir explicitement RED = 2" est tout l'avantage (dans d'autres langues) de pouvoir déclarer un nom de variable "constant"!
ToolmakerSteve
6
Auriez-vous avantage à bloquer cela? La chose la plus utile à propos de const est généralement les optimisations du compilateur qui ne sont pas vraiment une chose en Python. Vous voulez que quelque chose soit constant? Ne le changez pas. Si vous craignez que quelqu'un d'autre le change, vous pouvez simplement le mettre hors de leur portée, ou tout simplement réaliser que, si quelqu'un le change, c'est son problème et il doit y faire face, pas vous.
Kevin
@Kevin: "En bénéficieriez-vous ... ", l'avantage d' staticavoir un seul stockage pour la valeur de toutes les instances d'une classe? Sauf s'il existe une possibilité de déclarer une variable statique / classe.
min
8
Le problème racine est que certains peuvent le voir comme une valeur qui est une source de vérité, impossible à changer, et l'utiliser comme source de vérité dans tout leur code au lieu d'introduire des valeurs magiques (ce que je vois beaucoup en Python) - et d'autres peuvent le voir comme quelque chose qu'ils peuvent changer à volonté. Lorsque quelqu'un modifie une variable globale et que vous ne pouvez pas dire où elle a été modifiée et que l'application se bloque parce que RED = "bleu" au lieu de "rouge", vous introduisez un problème totalement inutile qui a déjà été résolu si simplement et est universellement compris.
Dagrooms du
5

Nous pouvons créer un objet descripteur.

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) Si nous voulions travailler avec des constantes au niveau de l'instance, alors:

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) si nous voulions créer des constantes uniquement au niveau de la classe, nous pourrions utiliser une métaclasse qui sert de conteneur pour nos constantes (nos objets descripteurs); toutes les classes qui descendent hériteront de nos constantes (nos objets descripteurs) sans aucun risque modifiable.

# 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

Si je crée une sous-classe de Foo, cette classe héritera de la constante sans possibilité de les modifier

class Bar(Foo): pass

>>> Bar.NUM  #=> 255
>>> Bar.NUM = 0  #=> ValueError: You can't change a constant
MVP
la source
4

Les dictionnaires Python sont modifiables, ils ne semblent donc pas être un bon moyen de déclarer des constantes:

>>> constants = {"foo":1, "bar":2}
>>> print constants
{'foo': 1, 'bar': 2}
>>> constants["bar"] = 3
>>> print constants
{'foo': 1, 'bar': 3}
n8boyd
la source
4

Voici une astuce si vous voulez des constantes et que vous ne vous souciez pas de leurs valeurs:

Définissez simplement des classes vides.

par exemple:

class RED: 
    pass
class BLUE: 
    pass
Lym Zoy
la source
4

En python, une constante est simplement une variable avec un nom dans toutes les capitales, avec des mots séparés par le caractère de soulignement,

par exemple

DAYS_IN_WEEK = 7

La valeur est modifiable, car vous pouvez la modifier. Mais étant donné que les règles du nom vous disent que c'est une constante, pourquoi le feriez-vous? Je veux dire, c'est votre programme après tout!

C'est l'approche adoptée tout au long de python. Il n'y a pas de privatemot-clé pour la même raison. Préfixez le nom avec un trait de soulignement et vous savez qu'il est destiné à être privé. Le code peut enfreindre la règle ... tout comme un programmeur pourrait de toute façon supprimer le mot-clé privé.

Python aurait pu ajouter un constmot - clé ... mais un programmeur pourrait supprimer le mot-clé puis changer la constante s'il le souhaite, mais pourquoi faire cela? Si vous souhaitez enfreindre la règle, vous pouvez quand même la modifier. Mais pourquoi s'embêter à enfreindre la règle si le nom rend l'intention claire?

Peut-être y a-t-il un test unitaire où il est logique d'appliquer une modification à la valeur? Pour voir ce qui se passe pendant une semaine de 8 jours même si dans le monde réel le nombre de jours de la semaine ne peut pas être changé. Si la langue vous empêchait de faire une exception s'il n'y a qu'un seul cas, vous devez enfreindre la règle ... vous devrez alors arrêter de la déclarer comme constante, même si c'est toujours une constante dans l'application, et il y a juste ce cas de test unique qui voit ce qui se passe s'il est modifié.

Le nom tout en majuscule vous indique qu'il est destiné à être une constante. Voilà ce qui est important. Ce n'est pas un langage qui force les contraintes sur le code que vous avez le pouvoir de changer de toute façon.

Telle est la philosophie du python.

innov8
la source
4

Il n'y a pas de moyen parfait de le faire. Si je comprends bien, la plupart des programmeurs capitaliseront simplement l'identifiant, donc PI = 3.142 peut facilement être compris comme une constante.

D'un autre côté, si vous voulez quelque chose qui agit réellement comme une constante, je ne suis pas sûr que vous le trouverez. Avec tout ce que vous faites, il y aura toujours un moyen de modifier la "constante", donc ce ne sera pas vraiment une constante. Voici un exemple très simple et sale:

def define(name, value):
  if (name + str(id(name))) not in globals():
    globals()[name + str(id(name))] = value

def constant(name):
  return globals()[name + str(id(name))]

define("PI",3.142)

print(constant("PI"))

On dirait que cela fera une constante de style PHP.

En réalité, il suffit à quelqu'un de modifier la valeur:

globals()["PI"+str(id("PI"))] = 3.1415

C'est la même chose pour toutes les autres solutions que vous trouverez ici - même les plus intelligentes qui créent une classe et redéfinissent la méthode d'attribut set - il y aura toujours un moyen de les contourner. C'est comme ça que Python est.

Ma recommandation est d'éviter simplement les tracas et de simplement capitaliser vos identifiants. Ce ne serait pas vraiment une constante appropriée, mais là encore, rien ne le serait.

John
la source
4

Il existe un moyen plus propre de le faire avec namedtuple:

from collections import namedtuple


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

Exemple d'utilisation

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

Avec cette approche exacte, vous pouvez nommer vos constantes.

Juan Ignacio Sánchez
la source
Pour tous ceux qui lisent ceci, veuillez garder à l'esprit que si vous définissez un objet modifiable comme l'une de ces constantes, n'importe qui peut modifier sa valeur interne. par exemple, laisse bar = [1, 2, 3], alors, vous pouvez faire comme suit: CONSTS.bar [1] = 'a' et il ne sera pas rejeté. Soyez donc prudent à ce sujet.
Juan Ignacio Sánchez
Au lieu de cette méthode hacky, que j'ai faite juste pour le plaisir, je recommande plutôt d'utiliser le décorateur de propriété de Python.
Juan Ignacio Sánchez
4

Peut-être que la bibliothèque pconst vous aidera ( 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.

sim
la source
3

Vous pouvez utiliser StringVar ou IntVar, etc., votre constante est const_val

val = 'Stackoverflow'
const_val = StringVar(val)
const.trace('w', reverse)

def reverse(*args):
    const_val.set(val)
Nqobizwe
la source
2

Vous pouvez le faire avec collections.namedtupleet itertools:

import collections
import itertools
def Constants(Name, *Args, **Kwargs):
  t = collections.namedtuple(Name, itertools.chain(Args, Kwargs.keys()))
  return t(*itertools.chain(Args, Kwargs.values()))

>>> myConstants = Constants('MyConstants', 'One', 'Two', Three = 'Four')
>>> print myConstants.One
One
>>> print myConstants.Two
Two
>>> print myConstants.Three
Four
>>> myConstants.One = 'Two'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Jaunty Sankey
la source
2

(Ce paragraphe était censé être un commentaire sur ces réponses ici et , qui mentionnaientnamedtuple , mais il devient trop long pour être intégré dans un commentaire, alors, c'est parti.)

L'approche nommée double mentionnée ci-dessus est définitivement innovante. Par souci d'exhaustivité, cependant, à la fin de la section NamedTuple de sa documentation officielle , il se lit comme suit:

les constantes énumérées peuvent être implémentées avec des tuples nommés, mais il est plus simple et plus efficace d'utiliser une simple déclaration de classe:

class Status:
    open, pending, closed = range(3)

En d'autres termes, la documentation officielle préfère utiliser une manière pratique, plutôt que d'implémenter réellement le comportement en lecture seule. Je suppose que cela devient encore un autre exemple de Zen of Python :

Simple, c'est mieux que complexe.

la praticité bat la pureté.

RayLuo
la source
2

Voici une collection d'expressions idiomatiques que j'ai créées pour tenter d'améliorer certaines des réponses déjà disponibles.

Je sais que l'utilisation de constant n'est pas pythonique, et vous ne devriez pas le faire à la maison!

Cependant, Python est un langage tellement dynamique! Ce forum montre comment il est possible de créer des constructions qui ressemblent et se sentent comme des constantes. Cette réponse a pour objectif principal d'explorer ce qui peut être exprimé par la langue.

S'il vous plaît ne soyez pas trop dur avec moi :-).

Pour plus de détails, j'ai écrit un blog d'accompagnement sur ces idiomes .

Dans cet article, j'appellerai une variable constante une référence constante à des valeurs (immuables ou non). De plus, je dis qu'une variable a une valeur figée lorsqu'elle fait référence à un objet mutable qu'un code client ne peut pas mettre à jour sa ou ses valeurs.

Un espace de constantes (SpaceConstants)

Cet idiome crée ce qui ressemble à un espace de noms de variables constantes (aka SpaceConstants). Il s'agit d'une modification d'un extrait de code par Alex Martelli pour éviter l'utilisation d'objets de module. En particulier, cette modification utilise ce que j'appelle une fabrique de classes car dans la fonction SpaceConstants , une classe appelée SpaceConstants est définie et une instance de celle-ci est renvoyée.

J'ai exploré l'utilisation de la fabrique de classes pour implémenter une conception basée sur des politiques en Python dans stackoverflow et également dans un 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"

Un espace de valeurs figées (SpaceFrozenValues)

Ce prochain idiome est une modification des SpaceConstants dans laquelle les objets mutables référencés sont figés. Cette implémentation exploite ce que j'appelle la fermeture partagée entre les fonctions setattr et getattr . La valeur de l'objet mutable est copiée et référencée par le cache variable défini à l'intérieur de la fonction de fermeture partagée. Il forme ce que j'appelle une copie protégée contre la fermeture d'un objet mutable .

Vous devez être prudent lorsque vous utilisez cet idiome, car getattr renvoie la valeur du cache en effectuant une copie complète . Cette opération pourrait avoir un impact significatif sur les performances des gros objets!

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"

Un espace constant (ConstantSpace)

Cet idiome est un espace de noms immuable de variables constantes ou ConstantSpace . C'est une combinaison de la réponse incroyablement simple de Jon Betts dans stackoverflow avec une usine de classe .

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

Un espace figé (FrozenSpace)

Cet idiome est un espace de noms immuable de variables figées ou FrozenSpace . Il est dérivé du modèle précédent en faisant de chaque variable un propriété protégée par la fermeture de la classe FrozenSpace générée .

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'"
Victor Bazterra
la source
2

En Python, les constantes n'existent pas, mais vous pouvez indiquer qu'une variable est une constante et ne doit pas être modifiée en ajoutant CONST_ au début du nom de la variable et en déclarant qu'il s'agit d'une constante dans un commentaire:

myVariable = 0
CONST_daysInWeek = 7    # This is a constant - do not change its value.   
CONSTANT_daysInMonth = 30 # This is also a constant - do not change this value.

Alternativement, vous pouvez créer une fonction qui agit comme une constante:

def CONST_daysInWeek():
    return 7;

la source
1

Dans mon cas, j'avais besoin de tableaux de bord immuables pour une implémentation d'une bibliothèque de crypto contenant de nombreux nombres littéraux que je voulais assurer étaient constants.

Cette réponse fonctionne mais la tentative de réaffectation des éléments bytearray ne génère pas d'erreur.

def const(func):
    '''implement const decorator'''
    def fset(self, val):
        '''attempting to set a const raises `ConstError`'''
        class ConstError(TypeError):
            '''special exception for const reassignment'''
            pass

        raise ConstError

    def fget(self):
        '''get a const'''
        return func()

    return property(fget, fset)


class Consts(object):
    '''contain all constants'''

    @const
    def C1():
        '''reassignment to C1 fails silently'''
        return bytearray.fromhex('deadbeef')

    @const
    def pi():
        '''is immutable'''
        return 3.141592653589793

Les constantes sont immuables, mais l'affectation constante du tableau secondaire échoue silencieusement:

>>> c = Consts()
>>> c.pi = 6.283185307179586  # (https://en.wikipedia.org/wiki/Tau_(2%CF%80))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "consts.py", line 9, in fset
    raise ConstError
__main__.ConstError
>>> c.C1[0] = 0
>>> c.C1[0]
222
>>> c.C1
bytearray(b'\xde\xad\xbe\xef')

Une approche plus puissante, simple et peut-être encore plus «pythonique» implique l'utilisation d'objets memoryview (objets tampons dans <= python-2.6).

import sys

PY_VER = sys.version.split()[0].split('.')

if int(PY_VER[0]) == 2:
    if int(PY_VER[1]) < 6:
        raise NotImplementedError
    elif 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 for
        read-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)

L'affectation d'élément ConstArray est TypeError:

>>> C1 = ConstArray('deadbeef')
>>> C1[0] = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'ConstArray' object does not support item assignment
>>> C1[0]
222
jxqz
la source
1

J'écris une librairie util pour python const: kkconst - pypi support str, int, float, datetime

l'instance de champ const conservera son comportement de type de base.

Par exemple:

from __future__ import print_function
from kkconst import (
    BaseConst,
    ConstFloatField,
)

class MathConst(BaseConst):
    PI = ConstFloatField(3.1415926, verbose_name=u"Pi")
    E = ConstFloatField(2.7182818284, verbose_name=u"mathematical constant")  # Euler's number"
    GOLDEN_RATIO = ConstFloatField(0.6180339887, verbose_name=u"Golden Ratio")

magic_num = MathConst.GOLDEN_RATIO
assert isinstance(magic_num, ConstFloatField)
assert isinstance(magic_num, float)

print(magic_num)  # 0.6180339887
print(magic_num.verbose_name)  # Golden Ratio

plus de détails sur l'utilisation vous pouvez lire l'url pypi : pypi ou github

kaka_ace
la source
1

Vous pouvez encapsuler une constante dans un tableau numpy, la marquer en écriture seule et toujours l'appeler par index zéro.

import numpy as np

# declare a constant
CONSTANT = 'hello'

# put constant in numpy and make read only
CONSTANT = np.array([CONSTANT])
CONSTANT.flags.writeable = False
# alternatively: CONSTANT.setflags(write=0)

# call our constant using 0 index    
print 'CONSTANT %s' % CONSTANT[0]

# attempt to modify our constant with try/except
new_value = 'goodbye'
try:
    CONSTANT[0] = new_value
except:
    print "cannot change CONSTANT to '%s' it's value '%s' is immutable" % (
        new_value, CONSTANT[0])

# attempt to modify our constant producing ValueError
CONSTANT[0] = new_value



>>>
CONSTANT hello
cannot change CONSTANT to 'goodbye' it's value 'hello' is immutable
Traceback (most recent call last):
  File "shuffle_test.py", line 15, in <module>
    CONSTANT[0] = new_value
ValueError: assignment destination is read-only

bien sûr, cela ne protège que le contenu du numpy, pas la variable "CONSTANT" elle-même; vous pouvez toujours faire:

CONSTANT = 'foo'

et CONSTANTchangerait, mais cela déclencherait rapidement une TypeError la première fois CONSTANT[0]est appelée plus tard dans le script.

bien que ... je suppose que si vous avez changé à un moment donné

CONSTANT = [1,2,3]

maintenant, vous n'obtiendrez plus le TypeError. hmmmm ....

https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.setflags.html

litepresence
la source