W
Will Ware
Python has no inherent provision for a restrictive API that blocks
accesses to methods and variables outside an allowed set.
Inexperienced Python programmers may fail to adhere to an agreed-upon
API, directly accessing the private internals of a class. Adherence to
defined APIs is a good thing. This function allows a class to specify
its API, and raise AttributeErrors for disallowed accesses.
def restrictiveApi(klas):
class newklas:
def __init__(self, *args):
self.__inst = apply(klas, args)
def __getattr__(self, attr):
# If the attribute is in the permitted API, then return
# the correct thing, no matter who asks for it.
#
if attr in self.__inst._PUBLIC:
return getattr(self.__inst, attr)
# If the attribute is outside the permitted API, then
# return it only if the calling class is in the list of
# friends. Otherwise raise an AttributeError.
#
elif hasattr(self.__inst, '_FRIENDS'):
# find the class of the method that called us
try:
raise Exception
except:
import sys
tb = sys.exc_info()[2]
callerClass = tb.tb_frame.f_back.\
f_locals['self'].__class__
# if it's a friend class, return the requested thing
if callerClass.__name__ in self.__inst._FRIENDS:
return getattr(self.__inst, attr)
# if not a friend, raise an AttributeError
raise AttributeError, attr
return newklas
To use this, a class needs to define two class variables, _PUBLIC and
_FRIENDS, both being lists (or tuples) of strings. The _PUBLIC list
gives the names of all methods and variables that should be considered
public, i.e. any other class may use them. The _FRIENDS list gives the
names of classes that are allowed free access to all methods and
variables in the protected class. The _FRIENDS list is optional.
Having defined _PUBLIC and optionally _FRIENDS, use something like the
following to protect your class. Restricting the API will incur a
performance overhead, so it's best to do it under the control of some
sort of debug flag.
if debug_flag:
from restrictive import restrictiveApi
MyClass = restrictiveApi(MyClass)
======== Examples ==========
class ClassUnderTest:
# This class has a private variable called privateX. It can be
# set using the setX() method or gotten using the x() method.
# If another class appears in the _FRIENDS list, that class
# can access privateX directly.
#
_PUBLIC = ('x', 'setX')
_FRIENDS = ('FriendClass',)
def __init__(self, x): # __init__ is always callable by anybody
self.setX(x)
def x(self): # callable by anybody
return self.privateX
def setX(self, x): # callable by anybody
self.privateX = x
ClassUnderTest = restrictiveApi(ClassUnderTest)
class FriendClass:
def getX(self, cut):
return cut.privateX # this works fine
class StrangerClass:
def getX(self, cut):
return cut.privateX # this raises an AttributeError
accesses to methods and variables outside an allowed set.
Inexperienced Python programmers may fail to adhere to an agreed-upon
API, directly accessing the private internals of a class. Adherence to
defined APIs is a good thing. This function allows a class to specify
its API, and raise AttributeErrors for disallowed accesses.
def restrictiveApi(klas):
class newklas:
def __init__(self, *args):
self.__inst = apply(klas, args)
def __getattr__(self, attr):
# If the attribute is in the permitted API, then return
# the correct thing, no matter who asks for it.
#
if attr in self.__inst._PUBLIC:
return getattr(self.__inst, attr)
# If the attribute is outside the permitted API, then
# return it only if the calling class is in the list of
# friends. Otherwise raise an AttributeError.
#
elif hasattr(self.__inst, '_FRIENDS'):
# find the class of the method that called us
try:
raise Exception
except:
import sys
tb = sys.exc_info()[2]
callerClass = tb.tb_frame.f_back.\
f_locals['self'].__class__
# if it's a friend class, return the requested thing
if callerClass.__name__ in self.__inst._FRIENDS:
return getattr(self.__inst, attr)
# if not a friend, raise an AttributeError
raise AttributeError, attr
return newklas
To use this, a class needs to define two class variables, _PUBLIC and
_FRIENDS, both being lists (or tuples) of strings. The _PUBLIC list
gives the names of all methods and variables that should be considered
public, i.e. any other class may use them. The _FRIENDS list gives the
names of classes that are allowed free access to all methods and
variables in the protected class. The _FRIENDS list is optional.
Having defined _PUBLIC and optionally _FRIENDS, use something like the
following to protect your class. Restricting the API will incur a
performance overhead, so it's best to do it under the control of some
sort of debug flag.
if debug_flag:
from restrictive import restrictiveApi
MyClass = restrictiveApi(MyClass)
======== Examples ==========
class ClassUnderTest:
# This class has a private variable called privateX. It can be
# set using the setX() method or gotten using the x() method.
# If another class appears in the _FRIENDS list, that class
# can access privateX directly.
#
_PUBLIC = ('x', 'setX')
_FRIENDS = ('FriendClass',)
def __init__(self, x): # __init__ is always callable by anybody
self.setX(x)
def x(self): # callable by anybody
return self.privateX
def setX(self, x): # callable by anybody
self.privateX = x
ClassUnderTest = restrictiveApi(ClassUnderTest)
class FriendClass:
def getX(self, cut):
return cut.privateX # this works fine
class StrangerClass:
def getX(self, cut):
return cut.privateX # this raises an AttributeError