Come faccio a creare una costante in Python?


992

C'è un modo per dichiarare una costante in Python? In Java possiamo creare valori costanti in questo modo:

public static final String CONST_NAME = "Name";

Qual è l'equivalente della precedente dichiarazione della costante Java in Python?


6
in realtà il modo di creare variabili di sola lettura è possibile tramite la funzione proprietà / decoratore di python . la risposta di inv è un esempio di un uso personalizzato di quello. la proprietà è di uso più generale di quella, tuttavia, una buona analisi di come funziona è sugli attributi e sui metodi Python di Shalabh Chaturvedi .
n611x007,

20
IMHO, l'applicazione della costanza è "non pitonica". In Python 2.7 puoi persino scrivere True=False, e poi (2+2==4)==Trueritorna False.
osa,

8
Come suggeriscono altre risposte, non è possibile dichiarare le costanti. Ma puoi leggere questo PEP sulle convenzioni. ad esempio THIS_IS_A_CONSTANT
Rasika Perera,

34
@osa: non puoi farlo in Python 3 - SyntaxError: can't assign to keyword. Sembra una buona cosa.
naught101

3
Sorpreso questo non è stato menzionato fino ad ora, ma Enums sembrerebbe un buon modo per definire le costanti enumerate.
cs95,

Risposte:


973

No non c'è. Non è possibile dichiarare una variabile o un valore come costante in Python. Basta non cambiarlo.

Se sei in una classe, l'equivalente sarebbe:

class Foo(object):
    CONST_NAME = "Name"

in caso contrario, è giusto

CONST_NAME = "Name"

Ma potresti voler dare un'occhiata allo snippet di codice Costanti in Python di Alex Martelli.


A partire da Python 3.8, esiste typing.Finalun'annotazione variabile che indica ai controllori di tipi statici (come mypy) che la variabile non deve essere riassegnata. Questo è l'equivalente più vicino a quello di Java final. Tuttavia, in realtà non impedisce la riassegnazione :

from typing import Final

a: Final = 1

# Executes fine, but mypy will report an error if you run mypy on this:
a = 2

27
Piuttosto, fai ciò che è in "Costanti in Python", dovresti usare la funzione "proprietà" o decoratore.
Seth Johnson,

26
Le persone chiedono la stessa funzione in Perl. Esiste un modulo di importazione chiamato "usa costante", ma (AFAIK) è solo un wrapper per creare una piccola funzione che restituisce il valore. Faccio lo stesso in Python. Esempio:def MY_CONST_VALUE(): return 123
kevinarpe il

8
"No non c'è." È vero, ma basandomi sul lavoro di altre persone, ho aggiunto una risposta, molto più in basso, con una breve e semplice implementazione di "Costanti" per Python 2.7 (che manca di "enum"). Questi sono di sola lettura name.attributee possono contenere qualsiasi valore. La dichiarazione è semplice Nums = Constants(ONE=1, PI=3.14159, DefaultWidth=100.0), L'utilizzo è semplice print 10 + Nums.PI, tenta di modificare i risultati in exception Nums.PI = 22=> ValueError (..).
ToolmakerSteve

110
Basta non cambiarlo. mi hai reso felice
Ciao Angelo

89
"Basta non cambiarlo" non è affatto utile. Non risponde alla domanda e suggerirei che è stato rimosso.
Bartek Banachewicz,

354

Non esiste una constparola chiave come in altre lingue, tuttavia è possibile creare una proprietà con una "funzione getter" per leggere i dati, ma nessuna "funzione setter" per riscrivere i dati. Questo essenzialmente protegge l'identificatore dalla modifica.

Ecco un'implementazione alternativa che utilizza la proprietà class:

Si noti che il codice è tutt'altro che facile per un lettore che si interroga sulle costanti. Vedi la spiegazione di seguito

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

Spiegazione del codice:

  1. Definire una funzione constantche accetta un'espressione e la utilizza per costruire un "getter", una funzione che restituisce esclusivamente il valore dell'espressione.
  2. La funzione setter genera un TypeError in modo che sia di sola lettura
  3. Usa la constantfunzione che abbiamo appena creato come decorazione per definire rapidamente le proprietà di sola lettura.

E in qualche altro modo più vecchio stile:

(Il codice è piuttosto complicato, più spiegazioni di seguito)

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

Nota che il decoratore @apply sembra essere deprecato.

  1. Per definire l'identificatore FOO, i primi definiscono due funzioni (fset, fget - i nomi sono a mia scelta).
  2. Quindi utilizzare la propertyfunzione integrata per costruire un oggetto che può essere "impostato" o "get".
  3. Notare che i propertyprimi due parametri della funzione sono denominati fsete fget.
  4. Usa il fatto che abbiamo scelto questi stessi nomi per il nostro getter e setter e crea un dizionario di parole chiave usando il ** (doppio asterisco) applicato a tutte le definizioni locali di tale ambito per passare i parametri alla propertyfunzione

11
Sulla base della documentazione su AttributeErrore TypeError, penso che l'eccezione sollevata dovrebbe essere un nuovo errore, che propongo di nominare ConstantErroro qualcosa del genere, che è una sottoclasse di TypeError. La sezione nella documentazione che mi fa pensare che: docs.python.org/2/library/exceptions.html
ArtOfWarfare,

3
Sono sorpreso da questo codice. Perché i metodi FOO () e BAR () hanno il carattere self come argomento? Il mio IDE sottolinea le parentesi in rosso (errore "compila"). Mi sono stancato di metterci dentro ma poi ho un errore.
user3770060,

10
Andare a queste lunghezze evidenzia una chiara carenza nel linguaggio Python. Perché non hanno sentito il bisogno di aggiungere questo in Python 3. Non riesco a credere che nessuno l'abbia suggerito e semplicemente non riesco a vedere la logica che sta dietro un comitato che va 'nah, costanti? nah '.
Andrew S,

8
E la tua soluzione può ancora essere modificata da un determinato programmatore python usandoCONST.__dict__['FOO'] = 7
pppery

11
@OscarSmith, penso che migliorerebbe il design del 'codice auto documentato'. Quando esplicito nel codice che un valore non può cambiare, è più facile da capire che leggere tutto il codice sorgente e rendersi conto che un valore non cambia mai. Inoltre, blocca la possibilità che qualcuno cambi un valore che dovrebbe essere, beh, costante. Ricorda: esplicito è meglio di implicito.
Gabriel,

112

In Python invece di applicare qualcosa nel linguaggio, le persone usano convenzioni di denominazione, ad esempio __methodper metodi privati ​​e _methodper metodi protetti.

Quindi allo stesso modo puoi semplicemente dichiarare la costante come tutte le maiuscole es

MY_CONSTANT = "one"

Se vuoi che questa costante non cambi mai, puoi agganciare l'accesso agli attributi e fare trucchi, ma un approccio più semplice è dichiarare una funzione

def MY_CONSTANT():
    return "one"

L'unico problema è ovunque dovrai fare MY_CONSTANT (), ma di nuovo MY_CONSTANT = "one"è il modo corretto in Python (di solito).

Puoi anche usare namedtuple per creare costanti:

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

18
Fare def MY_CONSTANT(): return "one"non fermerà qualcuno, più avanti nel codice, facendo MY_CONSTANT = "two"(o ridichiando la funzione).
Matthew Schinckel,

6
@MatthewSchinckel si tratta di convenzione, anche la modifica di MY_CONSTANT non cambierà l'utilizzo MY_CONSTANT () ma genererà errori, e in Python se vuoi puoi cambiare qualcosa, nessun trucco intelligente può proteggerti.
Anurag Uniyal

3
Grazie per aver sollevato l'approccio namedtuple. Decisamente innovativo. Potresti anche trovare pertinente il mio "commento" qui .
RayLuo,

@MatthewSchinckel puoi ridefinire QUALCOSA in pitone, quindi non è proprio un buon punto.
cslotty,

@MatthewSchinckel L'idea non è di scrivere MY_CONSTANT = MY_CONSTANT(), ma di usarla MY_CONSTANT()come costante. Certo, questo. Ma questo va bene ed è molto in linea con il principio del pitone "Siamo tutti adulti qui" - vale a dire che raramente sarà vietato allo sviluppatore decidere di scavalcare una regola quando hanno buone ragioni e sanno cosa stanno facendo.
jonathan.scholbach il

69

Recentemente ho trovato un aggiornamento molto succinto a questo che genera automaticamente messaggi di errore significativi e impedisce l'accesso tramite __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'

Definiamo noi stessi come diventare un'istanza e quindi utilizzare gli slot per garantire che non possano essere aggiunti altri attributi. Ciò rimuove anche il __dict__percorso di accesso. Naturalmente, l'intero oggetto può ancora essere ridefinito.

Modifica: soluzione originale

Probabilmente mi manca un trucco qui, ma questo sembra funzionare per 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

La creazione dell'istanza consente al __setattr__metodo magico di avviare e intercettare i tentativi di impostare la FOOvariabile. Potresti lanciare un'eccezione qui se lo desideri. L'istanza dell'istanza sul nome della classe impedisce l'accesso direttamente tramite la classe.

È un dolore totale per un valore, ma potresti attaccare molto al tuo CONSToggetto. Avere una classe superiore, anche il nome della classe sembra un po 'brutto, ma penso che sia nel complesso abbastanza succinto.


11
Questa è la risposta migliore e più chiara, perché ha il minimo "meccanismo", ma la maggior funzionalità. Sollevare un'eccezione è importante però ... non un'opzione.
Erik Aronesty,

Ho elaborato un percorso più breve che produce automaticamente errori significativi ma è molto simile. Ho lasciato l'idea originale qui per il confronto.
Jon Betts,

È un peccato che tu abbia ancora bisogno di questo CONST.prefisso. Anche in situazioni multi-modulo questo sarà complicato.
Alfe,

1
Penso che in genere vorresti raggruppare le costanti in alcuni fasci correlati in quella situazione (piuttosto che avere un oggetto CONST gigante), quindi probabilmente non è una cosa così brutta.
Jon Betts,

Perché questa risposta è ancora così in basso ?! La __slots__soluzione è così elegante ed efficace. Da tutto ciò che ho letto, si tratta quasi del più vicino possibile alla creazione di costanti in Python. Grazie mille. E per tutti gli interessati, ecco una brillante e approfondita spiegazione della __slots__magia.
JohnGalt

34

Python non ha costanti.

Forse l'alternativa più semplice è definire una funzione per essa:

def MY_CONSTANT():
    return 42

MY_CONSTANT() ora ha tutte le funzionalità di una costante (più alcune fastidiose parentesi graffe).


1
Volevo solo aggiungere questo suggerimento, ma per fortuna ho fatto scorrere verso il basso le risposte a basso punteggio. Spero che venga ulteriormente votato e concordo pienamente sul fatto che abbia tutte le funzionalità di una costante ed è molto semplice e diretto. Osservando la quantità di codice del boilerplate in tutte le sofisticate soluzioni, trovo le parentesi graffe relativamente inquietanti.
yaccob,

1
questa è la risposta più semplice, anche se va notato che ha un certo sovraccarico e non fermerà gli idioti che modificano il valore di ritorno.
Eviterà

@MrMesees che modifica il valore restituito? Intendi modificare la fonte? Ma da questo non sei protetto nemmeno in C ++, dove le costanti (come constexpr) sono reali costanti dure.
Ruslan,

@Ruslan intendevo dire che poiché Python non ha constexpr, non fermerebbe il valore che viene modificato dopo che è stato riportato in un contesto esterno. In questo esempio non è stato fatto nulla per 42 per imporre lo stato congelato.
MrMesees

20

Oltre alle due risposte principali (basta usare le variabili con i nomi MAIUSCOLI o usare le proprietà per rendere i valori di sola lettura), voglio menzionare che è possibile usare le metaclasse per implementare costanti nominate . Fornisco una soluzione molto semplice utilizzando metaclasse in GitHub che può essere utile se si desidera che i valori siano più informativi sul loro tipo / nome:

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

Questo è Python leggermente più avanzato, ma comunque molto facile da usare e pratico. (Il modulo ha alcune funzionalità in più, comprese le costanti in sola lettura, vedere il suo README.)

Esistono soluzioni simili che fluttuano in vari repository, ma per quanto ne so o mancano di una delle caratteristiche fondamentali che mi aspetterei dalle costanti (come essere costanti o essere di tipo arbitrario), oppure hanno caratteristiche esoteriche che aggiungono che renderli meno generalmente applicabili. Ma YMMV, sarei grato per il feedback. :-)


3
Mi piace la tua implementazione su GitHub. Ero quasi pronto a scrivere una classe di base che implementasse la funzionalità di ricerca inversa, ma vedo che l'hai fatto e altro!
Kerr,

Grazie, @Kerr, è il primo feedback che ho ricevuto e mi ha reso felice. :-)
hans_meine,

Eccezionale. Ho appena provato questo. Bello avere questo come opzione. Sebbene non abbia deciso se mi preoccupo abbastanza dell'aspetto di sola lettura, utilizzare questo piuttosto che semplicemente fare def enum(**enums): return type('Enum', (), enums). Numbers = enum(ONE=1, TWO=2, THREE='three'), come da stackoverflow.com/a/1695250/199364 , sezione "Nelle versioni precedenti ..."
ToolmakerSteve

19

Le proprietà sono un modo per creare costanti. Puoi farlo dichiarando una proprietà getter, ma ignorando il setter. Per esempio:

class MyFinalProperty(object):

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

Puoi dare un'occhiata a un articolo che ho scritto per trovare altri modi per utilizzare le proprietà di Python.


Sotto soluzione valutata. Ho appena implementato questo dopo aver trovato questa pagina (non questa risposta) e ho cercato di aggiungerlo se non già. Volevo sottolineare l'utilità di questa risposta.
Marc

18

Modifica: aggiunto codice di esempio per Python 3

Nota: questa altra risposta sembra fornire un'implementazione molto più completa simile alla seguente (con più funzionalità).

Innanzitutto, crea una metaclasse :

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

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

Ciò impedisce di modificare le proprietà statiche. Quindi crea un'altra classe che utilizza quella metaclasse:

class Const(object):
    __metaclass__ = MetaConst

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

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

Oppure, se stai usando Python 3:

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

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

Ciò dovrebbe impedire la modifica di oggetti di scena. Per usarlo, eredita:

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

Ora i puntelli, accessibili direttamente o tramite un'istanza, dovrebbero essere costanti:

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

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

Ecco un esempio di sopra in azione. Ecco un altro esempio per Python 3.


10

È possibile utilizzare un namedtuple come soluzione alternativa per creare in modo efficace una costante che funziona allo stesso modo di una variabile finale statica in Java (una "costante" Java). Come soluzioni alternative, è un po 'elegante. (Un approccio più elegante sarebbe semplicemente migliorare il linguaggio Python --- che tipo di linguaggio ti consente di ridefinire math.pi? - ma sto divagando.)

(Mentre scrivo, mi rendo conto di un'altra risposta a questa domanda menzionata namedtuple, ma continuerò qui perché mostrerò una sintassi che si avvicina di più a ciò che ti aspetteresti in Java, poiché non è necessario creare un nome digita come namedtuple ti costringe a fare.)

Seguendo il tuo esempio, ricorderai che in Java dobbiamo definire la costante all'interno di una classe ; perché non hai menzionato un nome di classe, chiamiamolo Foo. Ecco la classe Java:

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

Ecco l'equivalente Python.

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

Il punto chiave che voglio aggiungere qui è che non hai bisogno di un Footipo separato (una "tupla nominata anonima" sarebbe carino, anche se suona come un ossimoro), quindi chiamiamo la nostra tupla nominata in _Foomodo che speriamo che non lo faccia passare ai moduli di importazione.

Il secondo punto qui è che creiamo immediatamente un'istanza della nametuple, chiamandola Foo; non è necessario farlo in un passaggio separato (a meno che non lo si desideri). Ora puoi fare quello che puoi fare in Java:

>>> Foo.CONST_NAME
'Name'

Ma non puoi assegnargli:

>>> Foo.CONST_NAME = 'bar'

AttributeError: can't set attribute

Riconoscimento: ho pensato di aver inventato l'approccio namedtuple, ma poi vedo che qualcun altro ha dato una risposta simile (anche se meno compatta). Poi ho anche notato Cosa sono le "tuple nominate" in Python? , che sottolinea che sys.version_infoora è un nome chiamato, quindi forse la libreria standard di Python ha già avuto questa idea molto prima.

Sfortunatamente, purtroppo (essendo ancora Python), puoi cancellare del tutto l'intero Foocompito:

>>> Foo = 'bar'

(Facepalm)

Ma almeno stiamo impedendo che il Foo.CONST_NAMEvalore venga modificato, e questo è meglio di niente. In bocca al lupo.


Grazie per aver sollevato l'approccio namedtuple. Decisamente innovativo. Potresti anche trovare pertinente il mio "commento" qui .
RayLuo,

10

PEP 591 ha il qualificatore 'finale'. L'applicazione dipende dal controllo del tipo.

Quindi puoi fare:

MY_CONSTANT: Final = 12407

Nota: la Final parola chiave è applicabile solo per la versione Python 3.8


9

Ecco un'implementazione di una classe "Costanti", che crea istanze con attributi di sola lettura (costanti). Ad esempio, è possibile utilizzare Nums.PIper ottenere un valore che è stato inizializzato come 3.14159e Nums.PI = 22genera un'eccezione.

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

Grazie a FrozenDict di @MikeGraham , che ho usato come punto di partenza. Modificato, quindi al posto della Nums['ONE']sintassi di utilizzo è Nums.ONE.

E grazie alla risposta di @ Raufio, per l'idea di sostituire __ setattr __.

O per un'implementazione con più funzionalità, vedere le costanti nominate di @Hans_meine su GitHub


2
Python è un linguaggio di adulti consenzienti. Non c'è protezione contro qualcosa di simile. Nums._d['PI'] = 22 Il linguaggio stesso non fornisce alcun modo per contrassegnare le cose come non mutabili, credo.
Ajay M,

8

Una tupla si qualifica tecnicamente come costante, poiché una tupla genererà un errore se si tenta di modificarne uno. Se vuoi dichiarare una tupla con un valore, posiziona una virgola dopo il suo unico valore, in questo modo:

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

Per verificare il valore di questa variabile, utilizzare qualcosa di simile a questo:

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

Se si tenta di modificare questo valore, verrà generato un errore.


7

Vorrei creare una classe che sovrascrive il __setattr__metodo della classe di oggetti di base e avvolgere le mie costanti con quello, nota che sto usando 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)

Per avvolgere una stringa:

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

È piuttosto semplice, ma se vuoi usare le tue costanti come faresti con un oggetto non costante (senza usare constObj.value), sarà un po 'più intenso. È possibile che ciò possa causare problemi, quindi potrebbe essere meglio mantenere lo .valueshow e sapere che stai facendo operazioni con costanti (forse non il modo più 'pitonico').


+1 per un approccio interessante. Sebbene non sia pulito come le risposte che erano già state fornite. E anche la soluzione def ONE(): return 1più semplice suggerita in precedenza è più facile da usare ONE()di questa risposta ONE.value.
ToolmakerSteve

7

Sfortunatamente il Python non ha ancora delle costanti ed è un peccato. ES6 ha già aggiunto costanti di supporto a JavaScript ( https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/const ) poiché è una cosa molto utile in qualsiasi linguaggio di programmazione. Come risposta in altre risposte nella comunità Python, usa la costante maiuscola della convenzione come utente come costanti, ma non protegge da errori arbitrari nel codice. Se ti piace, potresti trovare utile una soluzione a file singolo come la prossima (vedi docstrings come usarla).

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

Se ciò non bastasse, vedere la versione di prova completa.

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

Vantaggi: 1. Accesso a tutte le costanti per l'intero progetto 2. Controllo rigoroso per i valori delle costanti

Manca: 1. Non supporta i tipi personalizzati e il tipo 'dict'

Appunti:

  1. Testato con Python3.4 e Python3.5 (sto usando 'tox' per questo)

  2. Ambiente di 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

Puoi migliorarlo leggermente convertendo automaticamente i dizionari in tuple nominate
Peter Schorn

6

Il modo Pythonic di dichiarare "costanti" è sostanzialmente una variabile a livello di modulo:

RED = 1
GREEN = 2
BLUE = 3

E poi scrivi le tue lezioni o funzioni. Dato che le costanti sono quasi sempre numeri interi e sono anche immutabili in Python, hai pochissime possibilità di modificarlo.

A meno che, ovviamente, se si imposta esplicitamente RED = 2.


21
Sì, ma bloccare la possibilità di "impostare esplicitamente RED = 2" è l'intero vantaggio (in altre lingue) di poter dichiarare "costante" un nome di variabile!
ToolmakerSteve

6
Ne trarrebbe beneficio dal bloccare questo? La cosa più utile di const è di solito l'ottimizzazione del compilatore che non è proprio una cosa in Python. Vuoi che qualcosa sia costante? Basta non cambiarlo. Se ti preoccupi che qualcun altro lo cambi, potresti semplicemente metterlo al di fuori del loro campo di applicazione, o semplicemente rendersi conto che, se qualcuno lo sta cambiando, questo è il loro problema e devono affrontarlo, non tu.
Kevin,

@Kevin: "Ne trarrebbe vantaggio ... ", il vantaggio di staticavere un unico archivio per il valore per tutte le istanze di una classe? A meno che non vi sia la possibilità di dichiarare effettivamente una variabile statica / di classe.
Min.

8
Il problema alla radice è che alcuni potrebbero vederlo come un valore che è una fonte di verità, incapace di essere cambiato, e usarlo come fonte di verità in tutto il loro codice invece di introdurre valori magici (cosa che vedo molto in Python) - e altri potrebbero vederlo come qualcosa che possono cambiare a piacimento. Quando qualcuno cambia una variabile globale e non puoi dire dove è stata cambiata e l'applicazione si blocca perché RED = "blue" anziché "red", stai introducendo un problema totalmente inutile che è già stato risolto in modo così semplice e è universalmente compreso.
Dagrooms,

5

Possiamo creare un oggetto descrittore.

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) Se volessimo lavorare con le costanti a livello di istanza, allora:

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) se volessimo creare costanti solo a livello di classe, potremmo usare una metaclasse che funge da contenitore per le nostre costanti (i nostri oggetti descrittori); tutte le classi che discendono erediteranno le nostre costanti (i nostri oggetti descrittori) senza alcun rischio che possa essere modificato.

# 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

Se creo una sottoclasse di Foo, questa classe erediterà la costante senza la possibilità di modificarle

class Bar(Foo): pass

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

4

I dizionari Python sono mutabili, quindi non sembrano un buon modo per dichiarare le costanti:

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

4

Ecco un trucco se vuoi costanti e non ti importa dei loro valori:

Basta definire le classi vuote.

per esempio:

class RED: 
    pass
class BLUE: 
    pass

4

In Python, una costante è semplicemente una variabile con un nome in tutte le maiuscole, con le parole separate dal carattere di sottolineatura,

per esempio

DAYS_IN_WEEK = 7

Il valore è modificabile, come in puoi modificarlo. Ma date le regole per il nome ti dicono che è una costante, perché dovresti? Voglio dire, dopo tutto è il tuo programma!

Questo è l'approccio adottato in Python. Non esiste una privateparola chiave per lo stesso motivo. Prefisso il nome con un carattere di sottolineatura e sai che deve essere privato. Il codice può infrangere la regola .... proprio come un programmatore potrebbe rimuovere comunque la parola chiave privata.

Python avrebbe potuto aggiungere a const parola chiave ... ma un programmatore potrebbe rimuovere la parola chiave e quindi modificare la costante se lo desidera, ma perché farlo? Se vuoi infrangere la regola, puoi comunque cambiare la regola. Ma perché preoccuparsi di infrangere la regola se il nome chiarisce l'intenzione?

Forse c'è qualche unit test in cui ha senso applicare una modifica al valore? Per vedere cosa succede per una settimana di 8 giorni anche se nel mondo reale il numero di giorni della settimana non può essere modificato. Se la lingua ti ha impedito di fare un'eccezione se c'è solo questo caso, devi infrangere la regola ... dovresti quindi smettere di dichiararla come costante, anche se è ancora una costante nell'applicazione, e c'è solo questo caso di test che vede cosa succede se viene cambiato.

Il nome tutto in maiuscolo indica che deve essere una costante. Questo è ciò che è importante. Non è un linguaggio che impone vincoli sul codice che hai il potere di cambiare comunque.

Questa è la filosofia di Python.


4

Non esiste un modo perfetto per farlo. A quanto ho capito, la maggior parte dei programmatori capitalizzerà l'identificatore, quindi PI = 3.142 può essere facilmente compreso come una costante.

D'altra parte, se vuoi qualcosa che in realtà si comporta come una costante, non sono sicuro che lo troverai. Con qualsiasi cosa tu faccia, ci sarà sempre un modo per modificare la "costante", quindi non sarà davvero una costante. Ecco un esempio molto semplice e sporco:

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

Sembra che renderà una costante in stile PHP.

In realtà tutto ciò che serve a qualcuno per cambiare il valore è questo:

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

Questo è lo stesso per tutte le altre soluzioni che troverai qui - anche quelle intelligenti che creano una classe e ridefiniscono il metodo dell'attributo set - ci sarà sempre un modo per aggirarle. Ecco come è Python.

La mia raccomandazione è di evitare tutte le seccature e capitalizzare i tuoi identificatori. Non sarebbe davvero una costante adeguata, ma di nuovo nulla lo farebbe.


4

C'è un modo più pulito per farlo con namedtuple:

from collections import namedtuple


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

Esempio di utilizzo

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

Con questo approccio esatto puoi spaziare sui nomi delle tue costanti.


Per tutti coloro che stanno leggendo questo, per favore, tieni presente che, se imposti un oggetto mutevole come una di queste costanti, chiunque può modificarne il valore interno. ad esempio, lascia bar = [1, 2, 3], quindi, potresti fare come segue: CONSTS.bar [1] = 'a' e non verrà rifiutato. Quindi stai attento a questo.
Juan Ignacio Sánchez,

Invece di questo metodo hacky, che ho creato solo per divertimento, consiglio invece di usare il decoratore di proprietà di Python.
Juan Ignacio Sánchez,

4

Forse la libreria pconst ti aiuterà ( 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.


3

Puoi usare StringVar o IntVar, ecc., La tua costante è const_val

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

def reverse(*args):
    const_val.set(val)

2

Puoi farlo con collections.namedtuplee 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

2

(Questo paragrafo doveva essere un commento su quelle risposte qua e , che menzionavano namedtuple, ma sta diventando troppo lungo per essere inserito in un commento, quindi eccolo qui.)

L'approccio con nome nominato sopra menzionato è decisamente innovativo. Per completezza, tuttavia, alla fine della sezione NamedTuple della sua documentazione ufficiale , si legge:

le costanti enumerate possono essere implementate con tuple denominate, ma è più semplice ed efficiente utilizzare una semplice dichiarazione di classe:

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

In altre parole, il tipo di documentazione ufficiale preferisce utilizzare un modo pratico, piuttosto che implementare effettivamente il comportamento di sola lettura. Immagino che diventi l'ennesimo esempio di Zen of Python :

Semplice è meglio di complesso.

la praticità batte la purezza.


2

Qui è una raccolta di idiomi che ho creato nel tentativo di migliorare alcune delle risposte già disponibili.

So che l'uso di costante non è pitonico e non dovresti farlo a casa!

Tuttavia, Python è un linguaggio così dinamico! Questo forum mostra come è possibile la creazione di costrutti che sembrano e sembrano costanti. Questa risposta ha come scopo primario quello di esplorare ciò che può essere espresso dalla lingua.

Per favore, non essere troppo duro con me :-).

Per maggiori dettagli ho scritto un blog di accompagnamento su questi modi di dire .

In questo post, chiamerò una variabile costante a un riferimento costante a valori (immutabili o meno). Inoltre, dico che una variabile ha un valore congelato quando fa riferimento a un oggetto mutabile che un codice client non può aggiornare i suoi valori.

Uno spazio di costanti (SpaceConstants)

Questo linguaggio crea quello che sembra uno spazio dei nomi di variabili costanti (aka SpaceConstants). È una modifica di uno snippet di codice di Alex Martelli per evitare l'uso di oggetti modulo. In particolare, questa modifica utilizza ciò che chiamo factory factory perché all'interno della funzione SpaceConstants , una classe chiamata SpaceConstants è definita e ne viene restituita un'istanza.

Ho esplorato l'uso della factory di classe per implementare un design simile alla politica simile a Python in StackOverflow e anche in un post di blog .

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"

Uno spazio di valori congelati (SpaceFrozenValues)

Il prossimo linguaggio è una modifica degli SpaceConstants in cui vengono congelati gli oggetti mutabili di riferimento. Questa implementazione sfrutta quella che chiamo chiusura condivisa tra le funzioni setattr e getattr . Il valore dell'oggetto mutabile viene copiato e referenziato dalla cache variabile definita all'interno della chiusura condivisa della funzione. Forma quella che chiamo copia protetta dalla chiusura di un oggetto mutabile .

Devi fare attenzione a usare questo idioma perché getattr restituisce il valore della cache facendo una copia profonda. Questa operazione potrebbe avere un impatto significativo sulle prestazioni di oggetti di grandi dimensioni!

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"

Uno spazio costante (ConstantSpace)

Questo linguaggio è uno spazio dei nomi immutabile di variabili costanti o ConstantSpace . È una combinazione della risposta incredibilmente semplice di Jon Betts nello stackoverflow con una fabbrica di 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'"

Uno spazio congelato (FrozenSpace)

Questo linguaggio è uno spazio dei nomi immutabile di variabili congelate o FrozenSpace . È derivato dal modello precedente rendendo ogni variabile una proprietà protetta dalla chiusura della classe FrozenSpace generata .

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

2

In Python, le costanti non esistono, ma puoi indicare che una variabile è una costante e non deve essere modificata aggiungendo CONST_all'inizio del nome della variabile e affermando che è una costante in un commento:

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.

In alternativa, puoi creare una funzione che agisce come una costante:

def CONST_daysInWeek():
    return 7;

1

Nel mio caso, avevo bisogno di filtri immutabili per l'implementazione di una libreria crittografica contenente molti numeri letterali che volevo garantire fossero costanti.

Questa risposta funziona ma il tentativo di riassegnazione di elementi bytearray non genera un errore.

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

Le costanti sono immutabili, ma l'assegnazione costante dello schermo non riesce in silenzio:

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

Un approccio più potente, semplice e forse anche più "pythonic" prevede l'uso di oggetti memoryview (oggetti buffer in <= 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'assegnazione degli articoli ConstArray è una 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

1

Scrivo una utility lib per python const: kkconst - pypi support str, int, float, datetime

l'istanza del campo const manterrà il suo comportamento di tipo base.

Per esempio:

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

maggiori dettagli sull'utilizzo è possibile leggere l'URL pypi : pypi o github


1

Puoi avvolgere una costante in un array intorpidito, contrassegnarla solo come scrittura e chiamarla sempre per indice zero.

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

ovviamente questo protegge solo il contenuto del numpy, non la variabile "COSTANTE" stessa; puoi ancora fare:

CONSTANT = 'foo'

e CONSTANTcambierebbe, tuttavia ciò genererebbe rapidamente un TypeError la prima volta che CONSTANT[0]viene successivamente chiamato nello script.

anche se ... suppongo che a un certo punto lo abbia cambiato in

CONSTANT = [1,2,3]

ora non otterresti più TypeError. Hmmmm ....

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.