我有cx_Oracle.Connection
名为x
的实例,我正在尝试print
x.clientinfo
或x.module
并获取:
attribute 'module' of 'cx_Oracle.Connection' objects is not readable
(奇怪的是,我可以打印x.username
)
我仍然可以dir(x)
取得成功,我没有时间查看cx_Oracle
的源代码(很多是用C实现的),所以我想知道实现者是怎么做的能做到这一点?是滚动描述符吗?或与__getitem__
相关的东西?这会是什么动机?
答案 0 :(得分:2)
您可以使用自定义描述符在Python中轻松完成此操作。
查看HOWTO中的Descriptor Example。如果您只是更改__get__
方法以引发AttributeError
......就是这样。我们不妨将其重命名并删除日志记录内容以使其更简单。
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
请注意,在2.x中,如果您忘记(object)
并创建经典类,则描述符将不起作用。 (我相信描述符他们自己实际上可以是经典类......但是不要这样做。)在3.x中,没有经典类,所以这不是问题。
那么,如果值是只写的,你会怎么读它?
嗯,这个玩具示例没用。但是你可以,例如,在obj
而不是你自己设置一些私有属性,此时知道数据存储位置的代码可以找到它,但偶然内省不能。
但你甚至不需要描述符。如果你想要一个只写的属性,无论你把它附加到哪个类,那都是一回事,但是如果你只是想阻止对特定类的某些成员的读访问,那么有一种更简单的方法:
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
有关详细信息,请参阅文档中data model一章中的__getattr__
和__getattribute__
文档。
在2.x中,如果你离开(object)
并创建一个经典类,属性查找的规则是完全不同的,并没有完全记录,你真的不想学习它们,除非你我计划在90年代花费大量时间,所以......不要这样做。此外,2.x显然需要2.x风格的显式super
调用,而不是3.x风格的魔术super()
。
从C API方面,你有大多数相同的钩子,但它们有点不同。有关详细信息,请参阅PyTypeObject
s,但基本上是:
tp_getset
允许您使用getter和setter函数自动构建描述符,类似于@property
但不相同。tp_descr_get
和tp_descr_set
分别用于构建描述符。tp_getattro
和tp_setattro
与__getattr__
和__setattr__
类似,不同之处在于它们被调用的时间略有不同,您通常会调用{{1}当你知道你没有需要挂钩属性访问的基类时,而不是委托给PyObject_GenericGetAttr
。仍然,为什么 你会这样做?
就个人而言,我已经完成了这样的事情,以了解有关Python数据模型和描述符的更多信息,但这并不是将其放入已发布库中的理由。
我猜测,有人这样做是因为他们试图在Python上强制错误地使用OO封装(基于传统的C ++模型) - 或者更糟糕的是,尝试构建Java风格security-by-encapsulation(如果没有安全的类加载器以及它附带的所有内容,它就无法工作)。
但是可能存在一些通用代码通过内省使用这些对象的情况,并且“欺骗”代码可能以试图欺骗人类用户的方式有用。例如,想象一个尝试pickle或JSON-ify或所有属性的序列化库。您可以轻松地将其写为忽略不可读的属性。 (当然,您可以轻松地制作它,例如,忽略以super()
为前缀的属性...)
至于为什么_
做到了......我从来没有看过它,所以我不知道。