Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to create a Python class method that does NOT pollute the attribute namespace of its instances?

I want to provide a method that can be used on a Python 2.7 class object, but does not pollute the attribute namespace of its instances. Is there any way to do this?

>>> class Foo(object):
...   @classmethod
...   def ugh(cls):
...     return 33
...
>>> Foo.ugh()
33
>>> foo = Foo()
>>> foo.ugh()
33
like image 414
Jason S Avatar asked Dec 18 '22 10:12

Jason S


2 Answers

You could subclass the classmethod descriptor:

class classonly(classmethod):
    def __get__(self, obj, type):
        if obj: raise AttributeError
        return super(classonly, self).__get__(obj, type)

This is how it would behave:

class C(object):
    @classonly
    def foo(cls):
        return 42
>>> C.foo()
42
>>> c=C()
>>> c.foo()
AttributeError

This desugars to the descriptor call (rather, it is invoked by the default implementation of __getattribute__):

>>> C.__dict__['foo'].__get__(None, C)
<bound method C.foo of <class '__main__.C'>>
>>> C.__dict__['foo'].__get__(c, type(c))
AttributeError

Required reading: Data Model — Implementing Descriptors and Descriptor HowTo Guide.

like image 80
Josh Lee Avatar answered Dec 21 '22 00:12

Josh Lee


ugh is not in the namespace:

>>> foo.__dict__
{}

but the rules for attribute lookup fall back to the type of the instance for missing names. You can override Foo.__getattribute__ to prevent this.

class Foo(object):
    @classmethod
    def ugh(cls):
        return 33

    def __getattribute__(self, name):
        if name == 'ugh':
            raise AttributeError("Access to class method 'ugh' block from instance")
        return super(Foo,self).__getattribute__(name)

This produces:

>>> foo = Foo()
>>> foo.ugh()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "tmp.py", line 8, in __getattribute__
    raise AttributeError("Access to class method 'ugh' block from instance")
AttributeError: Access to class method 'ugh' block from instance
>>> Foo.ugh()
33

You must use __getattribute__, which is called unconditionally on any attribute access, rather than __getattr__, which is only called after the normal lookup (which includes checking the type's namespace) fails.

like image 43
chepner Avatar answered Dec 20 '22 22:12

chepner