Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- # -*- coding: UTF-8 -*-
- """ Define the @frozenclass class decorator.
- The @frozenclass decorator provides capabilities to permit Python classes
- to be used in non-traditional ways as flexible data containers.
- Examples include:
- 1. Enums:
- Python users often implement enums using the pattern of class definitions
- containing only initialized class attributes (no methods). This pattern has
- a problem if the class is instantiated: The class attributes become mutable!
- Adding @frozenclass before the class makes class attributes immutable when
- the class is instantiated.
- @frozenclass
- class my_enum:
- MY_ENUM = 42 # A field with a value that can't be changed
- 2. Records:
- Records are data structures containing a fixed set of named variables.
- Python has no native capability like this: Python dictionaries provide
- named fields, but the set of fields is mutable. Python provides an
- immutable record facility via collections.namedtuple that implements a
- fixed set of fields, but their values are constant. Python frozendict
- (via frozenset) is similar, but has limitations similar to namedtuples.
- @frozenclass combines enums and records into a far more flexible pattern.
- Immutable fields are declared as class attributes. Mutable fields (instance
- attributes) are created only by __init__(). No new attributes can be added
- after instantiation, and only instance attributes (not class attributes) may
- have their values updated after instantiation.
- @frozenclass
- class my_record(object):
- def __init__():
- self.aField = 42 # A mutable field with a default init
- The user must craft an __init__() function if mutable attributes are
- desired. Optionally, __init__() may have parameters to permit flexible
- attribute initialization during instantiation. Any attribute may be
- initialized in this manner.
- @frozenclass
- class my_record(object):
- aConst = 15
- def __init__(aField=None):
- self.aField = aField # A field with an overridable default
- @frozenclass
- class my_record(object):
- aNonconfigurableConst = 12 # Not affected by __init__() below
- aConfigurableConst = 25 # This value is always stepped on!
- def __init__(aConfigurableConst = 42, aField=None):
- self.aConfigurableConst = aConfigurableConst
- self.aField = aField
- The @frozenclass decorator ALWAYS prevents adding new attributes after
- instantiation. It ALWAYS prevents changes to class attribute values after
- instantiation. It OPTIONALLY permits any attribute (both class and
- instance attributes) to be initialized as part of instantiation, if the
- user adds that capability as parameters to and code within the definition
- of the class __init__() function.
- CAVEAT: @frozenclass uses super() when __init__() is present, so your class
- will need to be a "new style" class. You will need to explicitly
- inherit from 'object' if you see this error:
- TypeError: must be type, not classobj
- Neither inheriting from object nor defining __init__() is needed for
- enum-only @frozenclasses.
- 3. "Smart" Records:
- The @frozenclass decorator is primarily intended to provide structures with
- varying degrees of field constness. But it can also do more, just like any
- other regular Python class.
- FUTURE: The present implementation is minimal, lacking desirable features:
- - The ability to export attributes and values as a dictionary or namedtuple.
- Status: Implemented in the separate asdict.py module.
- - Since the number of fields is fixed, attribute ordering can be supported,
- along with indexing, slicing, and iteratators.
- Status: For now, use functions in asdict to obtain similar behavior.
- - The __init__() function is boilerplate: It should be generated by the
- decorator. This could be done by making this a function decorator, where
- function parameters (with optional defaults) become mutable attributes,
- and function locals become immutable attributes. The decorated function
- would then become a 'factory function' similar to that created by
- collections.namedtuple.
- Status: Perhaps a 'namedclass' facility?
- - New mutable instance attributes may be created during instantiation by
- defining __init__() as follows:
- def __init__(self, *args, **kwargs):
- for key,val in kwargs.items():
- setattr(self, key, val)
- Status: It should be possible for the decorator to provide this service for
- enum-style classes (no __init__() and no inheritance), but I haven't found
- a way to make it work within the decorator. It works fine for the user.
- """
- def frozenclass(cls):
- """ A decorator to modify a class so that, after instantiation, class
- attribute values cannot be changed, and instance attributes cannot
- be created (but existing instance attribute values may be modified).
- """
- if hasattr(cls, '__init__'):
- _init = cls.__init__
- def init(self, *args, **kw):
- _init(self, *args, **kw)
- self.freeze = True
- else:
- def init(self, *args, **kw):
- self.freeze = True
- cls.__init__ = init
- def _setattr(self, attr, value):
- if getattr(self, "freeze", None): # Are we an initialized instance?
- # The attribute namespace is frozen:
- if attr=="freeze" or not hasattr(self, attr):
- raise AttributeError("You shall not create attributes!")
- # Class attributes are immutable:
- if hasattr(type(self), attr):
- raise AttributeError("You shall not modify immutable attributes!")
- super(cls, self).__setattr__(attr, value)
- cls.__setattr__ = _setattr
- return cls
- if __name__ == "__main__":
- # Let's test this puppy:
- @frozenclass
- class myClass(object):
- """ A demo class."""
- # The following are immutable after init:
- a = None; b = None; c = None
- def __init__(self, a, b, c, d=None, e=None, f=None):
- # We don't need to pass in initializers if all we want is to make
- # the class attribute values be true consts, even when instantiated.
- self.a = a; self.b = b; self.c = c # Immmutable after init
- self.d = d; self.e = e; self.f = f # Mutable
- # Tests
- try:
- a = myClass() # No init parameters
- except Exception, e:
- print "Expected error:", e
- try:
- a = myClass(1, 2) # Too few init parameters
- except Exception, e:
- print "Expected error:", e
- try:
- a = myClass(1, 2, 3, 4, 5, 6, 7) # Too many init parameters
- except Exception, e:
- print "Expected error:", e
- print "No error expected for correct partial initialization."
- a = myClass(1, 2, 3)
- print "No error expected for correct full initialization."
- a = myClass(1, 2, 3, 4, 5, 6)
- try:
- a.b = 5 # Attempt to assign immutable
- except Exception, e:
- print "Expected error:", e
- print "No error expected when modifying mutable attributes."
- a.e = 5
- try:
- a.g = "Fail!" # Try to create instance attribute
- except Exception, e:
- print "Expected error:", e
- @frozenclass
- class aClass(object):
- AN_ENUM = 15
- ANOTHER_ENUM = 25
- def __init__(self, *args, **kwargs):
- for key,val in kwargs.items():
- setattr(self, key, val)
- print "No error expected when adding no dynamic attributes."
- q = aClass()
- #print q.AN_ENUM, q.ANOTHER_ENUM
- print "No error expected when overriding class attribute initialization."
- r = aClass(AN_ENUM=16)
- #print r.AN_ENUM, r.ANOTHER_ENUM
- print "No error expected when adding a dynamic attribute."
- s = aClass(myField = 15)
- #print s.AN_ENUM, s.ANOTHER_ENUM, s.myField
- print "No error expected when updating a dynamic attribute."
- s.myField = 16
- #print s.AN_ENUM, s.ANOTHER_ENUM, s.myField
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement