Restricting methods in derived classes

Mark McEahern marklists at mceahern.com
Fri Sep 13 17:35:26 CEST 2002


Huaiyu,

The problem with my previously posted solution is that I was trying to
rewrite the class' methods at class __init__ time when I needed to be doing
that at __new__.  Here's a working model.  As usual, the names I pick are
probably plain goofy.  I'd be interested to know whehther this is what you
were looking for.

Cheers,

// mark

#!/usr/bin/env python

import inspect
import unittest

def make_unwanted(cls, method_name):
    def unwanted(self, *args, **kwargs):
        msg = "'%s' object has no attribute '%s'" % (cls.__name__,
method_name)
        raise AttributeError(msg)
    return unwanted

def is_unwanted(member, wanted_methods):
    return not member[0].startswith('__') and member[1] not in
wanted_methods

class Restricted(type):

    def __new__(cls, classname, bases, classdict):
        wanted_method_key = 'wanted_methods'
        # Don't do anything if we don't find the list of wanted methods.
        if wanted_method_key not in classdict:
            return type.__new__(cls, classname, bases, classdict)
        # Hide any method from bases not explicitly listed in
wanted_methods.
        wanted_methods = classdict[wanted_method_key]
        for base in bases:
            for method in inspect.getmembers(base,
inspect.ismethoddescriptor):
                if is_unwanted(method, wanted_methods):
                    classdict[method[0]] = make_unwanted(cls, method[0])
        return type.__new__(cls, classname, bases, classdict)

class RestrictedDict(dict):

    __metaclass__ = Restricted

    wanted_methods = [dict.update]

class tests(unittest.TestCase):

    def test_update(self):
        """Wanted methods should be accessible."""
        d = RestrictedDict()
        d['a'] = 1
        expected = {'a': 1}
        self.assertEquals(expected, d)
        d.update({'a': 2, 'b': 3})
        expected = {'a': 2, 'b': 3}
        self.assertEquals(expected, d)

    def test_error(self):
        """Unwanted methods should raise AttributeError."""
        d = RestrictedDict()
        d['a'] = 1
        self.assertRaises(AttributeError, d.copy)

    def test_using_base(self):
        """We should still be able to access base methods if we really want
to.

        <wink>
        """
        d = RestrictedDict()
        d['a'] = 1

        hidden_method_name = 'copy'
        for b in d.__class__.__bases__:
            if hasattr(b, hidden_method_name):
                copy_method = getattr(b, hidden_method_name)
                break
        d2 = copy_method(d)
        self.assertEquals(d2, d)

if __name__ == "__main__":
    unittest.main()





More information about the Python-list mailing list