Using a metaclass
You can customize the class __getattribute__
as in Schwobaseggl's answer - but you could also use a custom metaclass.
When we mention "metaclass" in Python, one ordinarily thinks of overriding its __new__
method and doing complicated things at class creation time (in contrast with instance creation time). However, if you leave all special dunder (__these__ __methods__
) aside, a metaclas is just a class's class - and all its methods will be visible from the class itself, but won't be visible from the class's instances. That means, they won't show up when one "dir"s an instance, but will show up when one "dir" the class - and won't be directly retrievable through the instance.
(Although, of course, one can always do self.__class__.method
)
Moreover, despite metaclasse's justified bad-fame of complexity, overriding __getattribute__
itself can have some pitfalls.
In this specific case, the classs you want to protect alreayd use a metaclass - but this particular use, unlike "ordinary" metaclass uses, can be freely composable just like an ordinary class hierarchy:
class ClsMethods(BaseModel):
# inherit from `type` if there is no metaclass already
# now, just leave __new__, __init__, __prepare__ , alone
# and write your class methods as ordinary methods:
def update(cls, *args, **kw):
...
def fetch_rows_from(self, ...):
...
class Model(with_metaclass(ClsMethods)):
# This really socks. Do you really still need Py2 support? :-)
...
(It should be obvious, but perceive you don't need to declare
the methods in the metaclass as classmethods: all of them are
classmethods for the metaclass instance, which is the class)
And a quick demo at the console:
In [37]: class M(type):
...: def secret(cls): print("At class only")
...:
In [38]: class A(metaclass=M):
...: pass
...:
In [39]: A.secret()
At class only
In [40]: A().secret()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-40-06355f714f97> in <module>()
----> 1 A().secret()
AttributeError: 'A' object has no attribute 'secret'
Creating a specialized decorator
Python's classmethod
decorator, and even ordinary instance methods, actually make use of the descriptor protocol: the methods, being objects themselves, have an specialized __get__
method which is used when retrieving them from an instance or from a class and modify the callable accordingly.
So, all we have to do is to create an equivalent of classmethod
which will disallow being called from an instance:
from functools import partial
class strict_classmethod:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is not None:
raise TypeError("This method cannot be called from instances")
return partial(self.func, owner)
class A:
@strict_classmethod
def secret(cls, ...):
...
This is a simple implementation that will work, but the decorated methods will still show up in class' introspection and dir
- however, it suffices to avoid calls by mistake.
classmethod
. You would have to write your own descriptor similar toclassmethod
that checks whether its being bound on an instance and raises an exception. – Sard