[ Python ]请问为什么这个 Class 是 iterable 的,如何修改 for 行为,具体内容请进帖

2013-10-10 16:34:49 +08:00
 sdjl
问题描述:
我正在使用 pymongo ,Cursor.find() 函数返回一个可迭代的 results ,比如

results = c.find()
for i in c:
print i

但是 i 是一个 dict ,因此不能这样使用 i.name, 而必须是 i['name']

所以我希望可以通过修改 c 的 next 函数让 i 变成 storage 类型(一种可以通过 i.name 代替 i['name']的类)

因此我使用了如下的办法:


这里的 c 如果为 instance 时此方法是可行的,但是 c 却是一个 Class ,因此此法行不通

读了一下 pymongo 的代码(/usr/lib/python2.7/dist-packages/pymongo )也没有找到原因

我想问的问题是:

1 、find 函数是如何返回 class ,而不是 instance 的? def find 的最后一句是:
return Cursor(self, *args, **kwargs)

2 、pymongo 是如何让 c (<class 'pymongo.cursor.Cursor'>)可迭代的?

3 、如何实现我想要的功能?
3649 次点击
所在节点    Python
17 条回复
mengzhuo
2013-10-10 16:45:34 +08:00
“所以我希望可以通过修改c的next函数让i变成storage类型(一种可以通过i.name代替i['name']的类)“

改写__getitem__成__getattr__,但是这不符合”属性必须明确“的原则。_所以继续用着吧_。

可迭代是添加__next__ __iter__两个函数
sdjl
2013-10-10 16:46:46 +08:00
for i in c:
print i

这里写错了, 应该是 for i in results
sdjl
2013-10-10 16:48:04 +08:00
@mengzhuo 抛错

AttributeError: 'Cursor' object has no attribute '__next__'
sdjl
2013-10-10 16:50:50 +08:00
注明,代码中id(it)和id(c)是相等的, print type(it)和print type(c)均显示:
<class 'pymongo.collection.Collection'>
keakon
2013-10-10 16:52:06 +08:00
1. 返回的就是 Cursor 类的对象。
2. 定义 __iter__ 和 next 方法。
3. next 是个方法,你把这个方法替换成了一个对象,然后去调用这个对象,自然会失败。
不建议你把 Cursor.next 方法给替换掉,你在使用时自己转换下又不会怀孕:
for i in c:
i = storage(**i)
sdjl
2013-10-10 16:52:58 +08:00
写错了, print type(it) 是 <class 'pymongo.cursor.Cursor'>
sdjl
2013-10-10 16:54:48 +08:00
@keakon 这里我可以把it.next和c.next,以及it.__iter__和c.__iter__均设置为None

也不会影响for语句的执行
sdjl
2013-10-10 17:02:38 +08:00
@keakon 嗯,返回的确实是instance, 用 inspect.isclass(results)查看了,不好意思
sdjl
2013-10-10 17:07:26 +08:00
如果我直接调用 results.next() 那么结果是正确的,能得到storage类型

所以我怀疑是否没用到pymongo.cursor.Cursor的next函数,尝试删除此next函数,for语句抛错,表明应该确实使用了此next函数

但是我在it(is c)上“重新定义”next函数,或删除next函数,均没有任何效果
sdjl
2013-10-10 17:17:22 +08:00
也就是说以下代码不会抛出异常,可以正常运行,很奇怪
binux
2013-10-10 17:20:28 +08:00
dict是内置对象
thedevil5032
2013-10-10 17:23:24 +08:00
piglei
2013-10-10 17:35:56 +08:00
刚刚我稍微读了一下pymongo的源码,pymongo的Cursor对象是靠实现__getitem__方法来实现可迭代对象的,例如:

# -*- coding: utf-8 -*-

class IterTester(object):

def __getitem__(self, index):
if index == 10:
raise StopIteration()
return index

if __name__ == '__main__':
for x in IterTester():
print x

具体的你可以再仔细看看源码。

另外如 @keakon 所说,不建议你修改Cursor的方法来实现类似功能,麻烦且不实用。
sdjl
2013-10-10 17:44:43 +08:00
@piglei 那就只能自己写一个继承类了吧? 我去掉__getitem__后for语句也能正确执行

hepochen
2013-10-10 17:46:37 +08:00
patch不是这种写法, patch之后,确保patch的代码,运行于所patch的类被实例化之前。

from pymongo.collection import Collection
old_func = Collection.find

def find(self, *args, **kwargs):
old_func(self, *args, **kwargs)
your_code = 'here'

Collection.find = find
hepochen
2013-10-10 17:59:13 +08:00
@sdjl __getitem__ 和 next 两个函数在pymongo中是应对不通场景的调用,所以都有,只去掉其中一个,呃,这仍然是可遍历的。

还是建议自己打patch,调用比较方便,也可以不用管后面的实现。之前有个版本,作者对__getitem__函数的处理就不一样,测试没有跑全就发布了,然后就bug了。
sdjl
2013-10-10 18:08:56 +08:00
@hepochen 嗯,也许只能这样更方便一些了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/85121

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX