Preventing class methods from being defined

D

David Hirschfield

Here's a strange concept that I don't really know how to implement, but
I suspect can be implemented via descriptors or metaclasses somehow:

I want a class that, when instantiated, only defines certain methods if
a global indicates it is okay to have those methods. So I want something
like:

global allow
allow = ["foo","bar"]

class A:
def foo():
...

def bar():
...

def baz():
...

any instance of A will only have a.foo() and a.bar() but no a.baz()
because it wasn't in the allow list.

I hope that makes sense.
Don't ask why I would need such a strange animal, I just do. I'm just
not sure how to approach it.

Should class A have some special metaclass that prevents those methods
from existing? Should I override __new__ or something? Should those
methods be wrapped with special property subclasses that prevent access
if they're not in the list? What's a low-overhead approach that will
work simply?

Thanks in advance,
-David
 
D

Dan Sommers

I want a class that, when instantiated, only defines certain methods
if a global indicates it is okay to have those methods. So I want
something like:
global allow
allow = ["foo","bar"]
class A:
def foo():
...
def bar():
...
def baz():
...
any instance of A will only have a.foo() and a.bar() but no a.baz()
because it wasn't in the allow list.
I hope that makes sense.

I think so, at least in the "I can implement that idea" sense, although
not the "why would you need such a strange animal" sense. Since "class"
is an executable statement in Python, this ought to do it:

allow = ["foo", "bar"]

class A:

if "foo" in allow:
def foo( ):
...

if "bar" in allow:
def bar( ):
...
Don't ask why I would need such a strange animal ...

Consider yourself not asked.

HTH,
Dan
 
D

David Hirschfield

I should have explicitly mentioned that I didn't want this particular
solution, for a number of silly reasons.
Is there another way to make this work, without needing to place an
explicit "if allowed" around each method definition?

Thanks again,
-David

Dan said:
I want a class that, when instantiated, only defines certain methods
if a global indicates it is okay to have those methods. So I want
something like:




global allow
allow = ["foo","bar"]




class A:
def foo():
...




def bar():
...




def baz():
...




any instance of A will only have a.foo() and a.bar() but no a.baz()
because it wasn't in the allow list.




I hope that makes sense.

I think so, at least in the "I can implement that idea" sense, although
not the "why would you need such a strange animal" sense. Since "class"
is an executable statement in Python, this ought to do it:

allow = ["foo", "bar"]

class A:

if "foo" in allow:
def foo( ):
...

if "bar" in allow:
def bar( ):
...


Don't ask why I would need such a strange animal ...

Consider yourself not asked.

HTH,
Dan
 
A

Alex Martelli

David Hirschfield said:
Here's a strange concept that I don't really know how to implement, but
I suspect can be implemented via descriptors or metaclasses somehow:

Yeah, a custom metaclass will do it, easily.
I want a class that, when instantiated, only defines certain methods if
a global indicates it is okay to have those methods. So I want something
like:

global allow
allow = ["foo","bar"]

class A:
def foo():
...

def bar():
...

def baz():
...

any instance of A will only have a.foo() and a.bar() but no a.baz()
because it wasn't in the allow list.

I hope that makes sense.

Sure. If you don't need to worry about inheritance, and want to
'snapshot' the set of methods and other class attributes based on the
value of 'allow' at the time the class statement executes:

class meta_only_allowed(type):
def __new__(mcl, name, bases, cdict):
for k in cdict.keys():
if k not in allow:
del cdict[k]
return super(mcl, meta_only_allowed).__new__(
mcl, name, bases, cdict)

[[untested, but I hope the concept is clear]].

If you want the value of 'allow' at class-*instantiation* time to be in
control, then what you need to override is __call__ rather than __new__.
You'll need to make a special class on the fly for the purpose (make
sure you memoize it to avoid needless duplication).

If you do need to worry about inheritance, and want to disallow
inherited methods as well, then you need to loop over all methods in
base classes (use standard library module inspect for that) and
construct an artificial dict, then drop the 'bases' and use an empty
tuple of bases for the supercall (real inheritance doesn't let you
"hide" methods, or other superclass attributes, ever).

Etc, etc -- it's most surely possible to do what you want, whatever what
you DO want is exactly!-)


Alex
 
S

Steven D'Aprano

Here's a strange concept that I don't really know how to implement, but
I suspect can be implemented via descriptors or metaclasses somehow:

I want a class that, when instantiated, only defines certain methods if
a global indicates it is okay to have those methods.

Others have already made suggestions, here is a third:


class A:
def foo(self):
print "Foo!"
def bar(self):
print "Bar!"
def baz(self):
print "Baz!"
__all__ = [x.__name__ for x in (foo, bar, baz)]
def __null(self, name="<unknown>", *args, **kwargs):
raise NameError("Method '%s' was disabled at init time." % name)
def __init__(self, data=None):
global permitted # not strictly needed, but I prefer it
for method in self.__all__:
if method not in permitted:
# if you are clever, use currying to bind the name of
# the method to the first arg of __null so it gives a
# more useful error message
setattr(self, method, self.__null)
# local initialisation
self.x = data


The main disadvantage of this I can see is that dir(A()) still reports
methods foo, bar, baz even if they have been disabled. But maybe that's
better behaviour than just making them disappear (principle of least
surprise: better to explicitly report that something is disabled than to
just have it magically appear and disappear).
 
D

Dan Sommers

Others have already made suggestions, here is a third:

class A:
def foo(self):
print "Foo!"
def bar(self):
print "Bar!"
def baz(self):
print "Baz!"
__all__ = [x.__name__ for x in (foo, bar, baz)]
def __null(self, name="<unknown>", *args, **kwargs):
raise NameError("Method '%s' was disabled at init time." % name)
def __init__(self, data=None):
global permitted # not strictly needed, but I prefer it
for method in self.__all__:
if method not in permitted:
# if you are clever, use currying to bind the name of
# the method to the first arg of __null so it gives a
# more useful error message
setattr(self, method, self.__null)
# local initialisation
self.x = data

The main disadvantage of this I can see is that dir(A()) still reports
methods foo, bar, baz even if they have been disabled. But maybe
that's better behaviour than just making them disappear (principle of
least surprise: better to explicitly report that something is disabled
than to just have it magically appear and disappear).

By the principle of least surprise, if dir(some_sobject) contains foo,
then some_object.foo should *not* raise a NameError.

All of the posted solutions to the OP's problem could easily be extended
to do something noisy with the (dis-)allowed methods.

Regards,
Dan
 
S

Steven D'Aprano

By the principle of least surprise, if dir(some_sobject) contains foo,
then some_object.foo should *not* raise a NameError.

Good thinking. Yes, it should raise a different exception.
 
B

Bengt Richter

I should have explicitly mentioned that I didn't want this particular
solution, for a number of silly reasons.
Is there another way to make this work, without needing to place an
explicit "if allowed" around each method definition?
Seems like you can
a. define the class with all methods defined within, and use a metaclass to prune
out the ones you don't want, which Alex provided.
b. define the class with conditional execution of the method definitions, which you just rejected.
c. define the class with no iffy methods at all, and add them afterwards
c1. in a metaclass that adds them and possibly also defines them for that purpose
c2. by plain statements adding method functions as class attributes
d. define all the methods normally, but monitor attribute access on the class and raise
attribute error for the methods that aren't supposed to be there.
e. raise an exception conditionally _within_ methods that aren't supposed to be there, if called.

What would you like?

BTW, defining the method functions someplace other than within the body of the class whose methods
they are to become has some subtleties, since the functions can potentially refer to different
global scopes than that of the class (e.g. if you take the functions from an imported module)
and/or use closure-defined cell variables (e.g. if the method function is defined within a factory function).
This can be used to advantage sometimes, but needs good documentation to be clear for the next code maintainer ;-)

I guess I should re-read your original requirements that led to thes design ideas.

Regards,
Bengt Richter
 
D

David Hirschfield

Thanks for this, it's a great list of the ways it can be done. Here's a
bit more insight into the arrangement I'm trying to get:

restrict = True

class A(object):
_restrict = ["test"]

def _null(self, *args, **kws):
raise Exception,"not allowed to access"

def test(self):
print "test restricted"

def __init__(self):
if restrict:
for f in self._restrict:
setattr(self,f,self._null)

class C(R):
def __init__(self):
super(C,self).__init__()

def test(self):
print "test from c"


In this design, calling c.test() where c is an instance of C will raise
an exception. Now, the only thing I'd like is to not have to fill out
that _restrict list like that, but to have some function or something
that let's me say which methods are restricted in the same way you
define class methods or properties, i.e.:

class A(object):
_restrict = []

def _null(self, *args, **kws):
raise Exception,"not allowed to access"

def test(self):
print "test restricted"
restrict(test)
#### this does some magic to insert "test" into the _restrict list


I can't really find a way to make that work with descriptors, and it
can't just be a function call, because I won't know what object to get
the _restrict list from. Is there a way to refer to the class that "is
being defined" when calling a function or classmethod?
So, ideas on how to accomplish that...again, greatly appreciated.
-Dave
 
B

Bengt Richter

Thanks for this, it's a great list of the ways it can be done. Here's a
Actually, your way is yet another ;-)
bit more insight into the arrangement I'm trying to get:

restrict = True
Why a global value? If it is to affect class instantiation, why not pass it
or a value to the constructor, e.g., C(True) or C(some_bool)?
class A(object): ^--should that be R?
_restrict = ["test"]

def _null(self, *args, **kws):
raise Exception,"not allowed to access"

def test(self):
print "test restricted"

def __init__(self):
if restrict:
for f in self._restrict:
setattr(self,f,self._null)
I assume you know that you are using a bound method attribute
on the instance to shadow the method of the class, for a per-instance
effect as opposed to an all-instances shared effect.
class C(R):
def __init__(self):
super(C,self).__init__()

def test(self):
print "test from c"


In this design, calling c.test() where c is an instance of C will raise
an exception. Now, the only thing I'd like is to not have to fill out
that _restrict list like that, but to have some function or something
that let's me say which methods are restricted in the same way you
define class methods or properties, i.e.:

class A(object):
_restrict = []

def _null(self, *args, **kws):
raise Exception,"not allowed to access"

def test(self):
print "test restricted"
restrict(test)
#### this does some magic to insert "test" into the _restrict list


I can't really find a way to make that work with descriptors, and it
can't just be a function call, because I won't know what object to get
the _restrict list from. Is there a way to refer to the class that "is
being defined" when calling a function or classmethod?
So, ideas on how to accomplish that...again, greatly appreciated.

You can do it with a decorator, though it doesn't really do decoration,
just adding the decoratee to the associated _restrict list. You don't
have to factor out mkrdeco if you'r'e only defining the restrict decorator
in one class.

I changed A to R, and made the global restriction flag a constructor argument,
but you can easily change that back, either by using the global restricted
in R.__init__ as a global, or by passing it explicitly like c = C(restricted).
... def restrict(f):
... rlist.append(f.func_name)
... return f
... return restrict
... ... _restrict = []
... restrict = mkrdeco(_restrict)
... def _null(self, *args, **kws):
... raise Exception,"not allowed to access"
... def __init__(self, restricted):
... if restricted:
... for f in self._restrict:
... setattr(self,f,self._null)
... @restrict
... def test(self):
... print "test restricted"
... ... def __init__(self, restricted=False):
... super(C,self).__init__(restricted)
...
... def test(self):
... print "test from c"
... Traceback (most recent call last):
File "<stdin>", line 1, in ?
['test']

Still don't know what real application problem this is solving, but that's ok ;-)

Regards,
Bengt Richter
 
D

David Hirschfield

Why a global value? If it is to affect class instantiation, why not pass it
or a value to the constructor, e.g., C(True) or C(some_bool)?
For reasons unrelated to this problem, the class that does this magic
can't take any parameters to its "__init__" method.
^--should that be R?
Yes, it should. Damn you Copy and Paste!
_restrict = ["test"]

def _null(self, *args, **kws):
raise Exception,"not allowed to access"

def test(self):
print "test restricted"

def __init__(self):
if restrict:
for f in self._restrict:
setattr(self,f,self._null)
I assume you know that you are using a bound method attribute
on the instance to shadow the method of the class, for a per-instance
effect as opposed to an all-instances shared effect.
Yes, that's true...it shouldn't really matter for my usage. What would I
do to make this an all-instances-shared thing?
class C(R):
def __init__(self):
super(C,self).__init__()

def test(self):
print "test from c"


In this design, calling c.test() where c is an instance of C will raise
an exception. Now, the only thing I'd like is to not have to fill out
that _restrict list like that, but to have some function or something
that let's me say which methods are restricted in the same way you
define class methods or properties, i.e.:

class A(object):
_restrict = []

def _null(self, *args, **kws):
raise Exception,"not allowed to access"

def test(self):
print "test restricted"
restrict(test)
#### this does some magic to insert "test" into the _restrict list


I can't really find a way to make that work with descriptors, and it
can't just be a function call, because I won't know what object to get
the _restrict list from. Is there a way to refer to the class that "is
being defined" when calling a function or classmethod?
So, ideas on how to accomplish that...again, greatly appreciated.

You can do it with a decorator, though it doesn't really do decoration,
just adding the decoratee to the associated _restrict list. You don't
have to factor out mkrdeco if you'r'e only defining the restrict decorator
in one class.

I changed A to R, and made the global restriction flag a constructor argument,
but you can easily change that back, either by using the global restricted
in R.__init__ as a global, or by passing it explicitly like c = C(restricted).
... def restrict(f):
... rlist.append(f.func_name)
... return f
... return restrict
...... _restrict = []
... restrict = mkrdeco(_restrict)
... def _null(self, *args, **kws):
... raise Exception,"not allowed to access"
... def __init__(self, restricted):
... if restricted:
... for f in self._restrict:
... setattr(self,f,self._null)
... @restrict
... def test(self):
... print "test restricted"
...... def __init__(self, restricted=False):
... super(C,self).__init__(restricted)
...
... def test(self):
... print "test from c"
...Traceback (most recent call last):
File "<stdin>", line 1, in ?
['test']

Still don't know what real application problem this is solving, but that's ok ;-)

Regards,
Bengt Richter
I'm using python 2.3.3 here, so no go on the nice decorator, but I can
definitely use the concept to make it work, thanks.
-David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top