Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write metaclass which would prevent creating new attributes after __init__()?

Currently I override the class' __setattr__() towards the end of the class' __init__() method to prevent new attribute creation -

class Point(object):
    def __init__(self):
        self.x = 0
        self.y = 0
        Point.__setattr__ = self._setattr

    def _setattr(self, name, value):
        if not hasattr(self, name):
            raise AttributeError("'" + name + "' not an attribute of Point object.")
        else:
            super(Point, self).__setattr__(name, value)

Is there a way to avoid manually overriding __setattr__() and do this automatically with the help of metaclasses?

The closest I came was -

class attr_block_meta(type):
    def __new__(meta, cname, bases, dctry):
        def _setattr(self, name, value):
            if not hasattr(self, name):
                raise AttributeError("'" + name + "' not an attribute of " + cname + " object.")
            object.__setattr__(self, name, value)

        dctry.update({'x': 0, 'y': 0})
        cls = type.__new__(meta, cname, bases, dctry)
        cls.__setattr__ = _setattr
        return cls

class ImPoint(object):
    __metaclass__ = attr_block_meta

Is there a more generic way of doing this such that apriori knowledge of the subclass attributes is not required?
Basically, how to avoid the line dctry.update({'x': 0, 'y': 0}) and make this work irrespective of what the names of class attributes are?

P.S. - FWIW I have already evaluated the __slots__ and namedtuple options and found them lacking for my needs. Please don't narrow your focus to the pared down Points() example that I have used to illustrate the question; the actual use case involves a far more complex class.

like image 219
work.bin Avatar asked Sep 24 '15 12:09

work.bin


People also ask

What is __ metaclass __ in Python?

A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.

How do you prevent dynamic creation of attributes in Python?

When we design a class, we can use slots to prevent the dynamic creation of attributes. To define slots, you have to define a list with the name __slots__ . The list has to contain all the attributes, you want to use.

How do you create a metaclass in Python?

To create your own metaclass in Python you really just want to subclass type . A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.

What is __ slots __ in Python?

__slots__ is a class variable. If you have more than one instance of your class, any change made to __slots__ will show up in every instance. You cannot access the memory allocated by the __slots__ declaration by using subscription. You will get only what is currently stored in the list.


1 Answers

Don't reinvent the wheel.

Two simple ways to achieve that (without directly using a metaclass) are using:

  1. namedtuples
  2. __slots__

For example, using namedtuple (based on the example in the docs):

Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)
p.z = 33  # ERROR

For example, using __slots__:

class Point(object):
    __slots__ = ['x', 'y']
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

p = Point(11,22)
p.z = 33  # ERROR
like image 125
shx2 Avatar answered Oct 11 '22 18:10

shx2