I have an instance of a cx_Oracle.Connection
called x
and I'm trying to print
x.clientinfo
or x.module
and getting:
attribute 'module' of 'cx_Oracle.Connection' objects is not readable
(What's weird is that I can do print x.username
)
I can still do dir(x)
with success and I don't have time to look at the source code of cx_Oracle
(lots of it implemented in C) so I'm wondering how the implementer was able to do this? Was it by rolling descriptors? or something related to __getitem__
? What would be the motivation for this?
You can do this pretty easily in Python with a custom descriptor.
Look at the Descriptor Example in the HOWTO. If you just change the __get__
method to raise an AttributeError
… that's it. We might as well rename it and strip out the logging stuff to make it simpler.
class WriteOnly(object):
"""A data descriptor that can't be read.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
raise AttributeError("No peeking at attribute '{}'!".format(self.name))
def __set__(self, obj, val):
self.val = val
class MyClass(object):
x = WriteOnly(0, 'x')
m = MyClass()
m.x = 20 # works
print(m.x) # raises AttributeError
Note that in 2.x, if you forget the (object)
and create a classic class, descriptors won't work. (I believe descriptors themselves can actually be classic classes… but don't do that.) In 3.x, there are no classic classes, so that's not a problem.
So, if the value is write-only, how would you ever read it?
Well, this toy example is useless. But you could, e.g., set some private attribute on obj
rather than on yourself, at which point code that knows where the data are stored can find it, but casual introspection can't.
But you don't even need descriptors. If you want an attribute that's write-only no matter what class you attach it to, that's one thing, but if you just want to block read access to certain members of a particular class, there's an easier way:
class MyClass(object):
def __getattribute__(self, name):
if name in ('x', 'y', 'z'):
raise AttributeError("No! Bad user! You cannot see my '{}'!".format(name))
return super().__getattribute__(self, name)
m = MyClass()
m.x = 20
m.x # same exception
For more details, see the __getattr__
and __getattribute__
documentation from the data model chapter in the docs.
In 2.x, if you leave the (object)
off and create a classic class, the rules for attribute lookup are completely different, and not completely documented, and you really don't want to learn them unless you're planning to spend a lot of time in the 90s, so… don't do that. Also, 2.x will obviously need the 2.x-style explicit super
call instead of the 3.x-style magic super()
.
From the C API side, you've got most of the same hooks, but they're a bit different. See PyTypeObject
s for details, but basically:
tp_getset
lets you automatically build descriptors out of getter and setter functions, which is similar to @property
but not identical.tp_descr_get
and tp_descr_set
are for building descriptors separately.tp_getattro
and tp_setattro
are similar to __getattr__
and __setattr__
, except that the rules for when they get called are a little different, and you typically call PyObject_GenericGetAttr
instead of delegating to super()
when you know you have no base classes that need to hook attribute access.Still, why would you do that?
Personally, I've done stuff like this to learn more about the Python data model and descriptors, but that's hardly a reason to put it in a published library.
I'm guessing that more often than not, someone does it because they're trying to force a mistaken notion of OO encapsulation (based on the traditional C++ model) on Python—or, worse, trying to build Java-style security-by-encapsulation (which doesn't work without a secure class loader and all that comes with it).
But there could be cases where there's some generic code that uses these objects via introspection, and "tricking" that code could be useful in a way that trying to trick human users isn't. For example, imagine a serialization library that tried to pickle or JSON-ify or whatever all of the attributes. You could easily write it ignore non-readable attributes. (Of course you could just as easily make it, say, ignore attributes prefixed with a _
…)
As for why cx_Oracle
did it… I've never even looked at it, so I have no idea.