写了一个类,实例化之后作为一个dic的key,但是出现 TypeError: unhashable instance。
我记得看过Learning Python中说过,甚至类的实例也可以作为字典的键,请问有什么特殊要求吗?
wolfg 回复于:2005-09-23 10:49:02
The object must be immutable
Rcfeng 回复于:2005-09-23 10:49:13
"And class instance objects (discussed in Part VI) can be used as keys too, as long as they have the proper protocol methods;roughly, they need to tell Python that their values won't change, or else they would be useless as fixed keys."
上面是Learning Python中说的,请问怎么告诉python我这个类的实例是不变的呢?
limodou 回复于:2005-09-23 10:56:14
我试了一下没有问题。你的代码是什么,可能是别的错误。
>;>;>; class A:
... pass
>;>;>; a=A()
>;>;>; b={a:1}
>;>;>; b[a]
1
Rcfeng 回复于:2005-09-23 11:05:15
你试着定义一个__init__,然后定义两个成员变量再试试呢?
class ip:
"A Ip class for finding user's area"
def __init__(self, low_ip, up_ip):
self.low_ip = low_ip
self.up_ip = up_ip
# def printip(self):
# print "Low: %d; Up: %d" % (self.low_ip,self.up_ip)
def __cmp__(self,ip):
if( ip>;=low_ip and ip<=up_ip ):
return 0
elif( ip<low_ip ):
return -1
else: return 1
ip_map[ip(ip1,ip2)] = text
这样是不行的~
wolfg 回复于:2005-09-23 11:21:41
引用:原帖由 "Rcfeng"]p2)] 这样是不行的~ 发表:
不使用内置的__cmp__方法就行了
class ip:
"A Ip class for finding user's area"
def __init__(self, low_ip, up_ip):
self.low_ip = low_ip
self.up_ip = up_ip
def __str__(self):
return "Low: %d; Up: %d" % (self.low_ip,self.up_ip)
def cmpare(self,ip):
if( ip>;=low_ip and ip<=up_ip ):
return 0
elif( ip<low_ip ):
return -1
else: return 1
可能是有了__cmp__就认为是可变的了
Python文档里找到这么一句
引用:Instances of a class normally compare as non-equal unless the class defines the __cmp__() method.
__cmp__( self, other)
Called by comparison operations if rich comparison (see above) is not defined. Should return a negative integer if self < other, zero if self == other, a positive integer if self >; other. If no __cmp__(), __eq__() or __ne__() operation is defined, class instances are compared by object identity (``address''). See also the description of __hash__() for some important notes on creating objects which support custom comparison operations and are usable as dictionary keys. (Note: the restriction that exceptions are not propagated by __cmp__() has been removed since Python 1.5.)
看了__hash__的介绍,终于明白了
引用:__hash__( self)
Called for the key object for dictionary operations, and by the built-in function hash(). Should return a 32-bit integer usable as a hash value for dictionary operations. The only required property is that objects which compare equal have the same hash value; it is advised to somehow mix together (e.g., using exclusive or) the hash values for the components of the object that also play a part in comparison of objects. If a class does not define a __cmp__() method it should not define a __hash__() operation either; if it defines __cmp__() or __eq__() but not __hash__(), its instances will not be usable as dictionary keys. If a class defines mutable objects and implements a __cmp__() or __eq__() method, it should not implement __hash__(), since the dictionary implementation requires that a key's hash value is immutable (if the object's hash value changes, it will be in the wrong hash bucket).
If a class does not define a __cmp__() method it should not define a __hash__() operation either; if it defines __cmp__() or __eq__() but not __hash__(), its instances will not be usable as dictionary keys.
Rcfeng 回复于:2005-09-23 11:34:02
我的初衷是想拦截ip_map[ip(ip1,ip2)] = text 这个dic的get方法,让他从完全匹配到,只要在low_ip和up_ip这个范围即匹配,不知道这个实现思想是不是正确呢?请版主指点。
def __cmp__(self,ip):
if( ip>;=self.low_ip and ip<=self.up_ip ):
return 0
elif( ip<self.low_ip ):
return -1
else: return 1
ip_map.get(ip3)
如果ip3在ip1和ip2的范围之内就相当于匹配,去除后面text中的描述
wolfg 回复于:2005-09-23 11:34:26
根据文档里的讲解,用__cmp__也可以,再实现__hash__就行了
我随便试了一下
def __hash__(self):
return hash(self.low_ip + self.up_ip)
是可以的,只是不知道这样hash会不会有问题
wolfg 回复于:2005-09-23 11:38:22
你可以这样做
不过要想用dict的话,就得自己实现__hash__方法,保证low_ip和up_ip值都一样的ip的instance其hash值一样,这样就可以做dict的key了
不知道说得对不对,呵呵
Rcfeng 回复于:2005-09-23 11:44:05
真是多谢啦,我试试先~
Rcfeng 回复于:2005-09-23 13:03:09
我仔细看了一下hash()的说明,必须__hash__的返回值和待比较的值相等时才能算是找到对应的key,唉,好像不是使用__cmp__来啊,这个和C++不同啊——只需要定义map,然后find,并重载operator<就可以了~
怎么办?
wolfg 回复于:2005-09-23 13:17:14
哦,原来理解错误了
你可以继承dictionary,实现一个自己的
wolfg 回复于:2005-09-23 13:27:26
引用:原帖由 "Rcfeng"]p2)] 如果ip3在ip1和ip2的范围之内就相当于匹配,去除后面text中的描述 发表:
怎么叫ip3在ip1和ip2的范围之内?能否举一个例子
Rcfeng 回复于:2005-09-23 13:31:28
是这样的,不知道你熟悉木子QQ的IP库不,他是按照
61.152.100.1 61.152.100.255 上海市
这样的格式来的,并且是按照ip值大小顺序从低到高。
我的想法是,使用一个class,将前面的两个ip范围作为成员变量,实例化一个类,然后把类作为key,把后面的地区作为valus,然后重载这个class的get()的匹配key的操作符,让他根据我输入的ip去查找他的所在的地区。
上面不知道我说清楚没有,呵呵。
下面是我的代码。
说一下我输入的infile的格式:
extrainf ip
第一列是一些附加信息,第二列是要查的ip
def formatip(ip):
if is_ip(ip):
ipstr = map(int,(ip.split('.')))
return (((ipstr[0]*256+ipstr[1])*256+ipstr[2])*256+ipstr[3])
else:
return ip
def is_ip(ip_str) :
"Check a string is a ip address or not"
parts = ip_str.split('.')
if len(parts) != 4 :
return False
for part in parts :
if not (part.isdigit() and int(part) <= 255 and int(part) >;= 0):
return False
return True
class ip:
"A Ip class for finding user's area"
def __init__(self, low_ip, up_ip):
self.low_ip = low_ip
self.up_ip = up_ip
# def printip(self):
# print "Low: %d; Up: %d" % (self.low_ip,self.up_ip)
# def __cmp__(self,ip):
# if( hash(ip)>;=hash(self.low_ip) and hash(ip)<=hash(self.up_ip) ):
# return 0
# elif( hash(ip)<hash(self.low_ip) ):
# return -1
# else: return 1
# def __eq__(self,ip):
# if( hash(ip)>;=hash(self.low_ip) and hash(ip)<=hash(self.up_ip) ):
# return True
# else: return False
# def __ne__(self,ip):
# if( hash(ip)<=hash(self.low_ip) or hash(ip)>;=hash(self.up_ip) ):
# return True
# else: return False
def __hash__(self):
print hash(self.low_ip)
return hash(self.low_ip)
if( __name__ == "__main__"):
import sys
if(len(sys.argv) != 3):
print "Not current arguments: %d" % len(sys.argv)
sys.exit()
try:
datafile=open(sys.argv[1],"r")
infile=open(sys.argv[2],"r")
except IOError:
print "Open file error,exiting..."
sys.exit()
ip_map={}
for line in datafile.readlines():
onearea = map(formatip,line.split())
ip_map[ip(onearea[0],onearea[1])] = onearea[2]
for userip in infile.readlines():
oneinf = userip.split()
loc=ip_map.get(formatip(oneinf[1]))
if( loc ):
print "Time:%s, Ip:%s, Area:%s" % (oneinf[0], oneinf[1], loc)
else: print "Time:%s, Ip:%s, Area:%s" % (oneinf[0], oneinf[1], "unknow")
Rcfeng 回复于:2005-09-23 13:33:15
另外,说一下,这个思路应该是没有问题的,因为我用C++用同样的思路,使用map.find(),并重载"operator<"是可以满足需求的。
Rcfeng 回复于:2005-09-23 13:48:52
不过还是比较奇怪,要是我把__cmp__注掉,那么即使dic的hash和输入值的hash相等也不会正确判断,真不知道他是怎么进行key的匹配的了,唉。看来要去看源码了,呵呵。
limodou 回复于:2005-09-23 13:56:01
如果你只是想实现get的特殊处理,那么可以自已从dict派生出一个类,然后覆盖get方法进行特殊处理即可。上面的方法似乎麻烦了一些。
Rcfeng 回复于:2005-09-23 13:58:25
其实我只是想重载get判断key是否相等的那个运算符,如果为了这个而去实现一个get查找算法,觉得有些得不偿失。其实为什么要用一个类实现呢?就是不想自己重新写一个容器的查找算法。呵呵
limodou 回复于:2005-09-23 14:56:38
那你完全可以让每个ip类提供一个判断的方法,如is_in(ip),然后在容器类中进行调用就行了。也不麻烦。
Rcfeng 回复于:2005-09-24 18:55:44
我尝试将ip_map容器用list来代替,使用index方法来查找ip,重载__cmp__,结果是可行的,查出来了,结果也是正确的。但是……速度慢得一塌糊涂,还不如自己写的遍历算法(几乎是自写算法的两倍)。
看来必须使用dic才行啊,它应该有一套内部优化的排序和查找算法,因为用之前dic做容器,虽然只能查到ip完全与范围匹配的情况,但是查找的速度很快。
唉,看来必须深入了解dic.get()方法的实现才行啦,遍历的方法太慢!下面是用list作容器的代码。
#!/usr/bin/python
def formatip(ip):
if is_ip(ip):
ipstr = map(int,(ip.split('.')))
return (((ipstr[0]*256+ipstr[1])*256+ipstr[2])*256+ipstr[3])
else:
return ip
def is_ip(ip_str) :
"Check a string is a ip address or not"
parts = ip_str.split('.')
if len(parts) != 4 :
return False
for part in parts :
if not (part.isdigit() and int(part) <= 255 and int(part) >;= 0):
return False
return True
class ip:
"A Ip class for finding user's area"
def __init__(self, low_ip, up_ip, loc):
self.low_ip = low_ip
self.up_ip = up_ip
self.loc = loc
# def printip(self):
# print "Low: %d; Up: %d" % (self.low_ip,self.up_ip)
def __cmp__(self,ip):
if( ip>;=self.low_ip and ip<=self.up_ip ):
return 0
elif( ip<self.low_ip ):
return -1
else: return 1
if( __name__ == "__main__"):
import sys
if(len(sys.argv) != 3):
print "Not current arguments: %d" % len(sys.argv)
sys.exit()
try:
datafile=open(sys.argv[1],"r")
infile=open(sys.argv[2],"r")
except IOError:
print "Open file error,exiting..."
sys.exit()
ip_map=[]
for line in datafile.readlines():
onearea = map(formatip,line.split())
ip_map.append(ip(onearea[0],onearea[1],onearea[2]))
for userip in infile.readlines():
oneinf = userip.split()
try:
loc=ip_map.index(formatip(oneinf[1]))
except ValueError:
loc = False
if( loc ):
print "Time:%s, Ip:%s, Area:%s" % (oneinf[0], oneinf[1], ip_map[loc].loc)
else: print "Time:%s, Ip:%s, Area:%s" % (oneinf[0], oneinf[1], "unknow")
limodou 回复于:2005-09-24 21:28:46
dict是一种hash算法,要比遍历快。而且算法简单清晰就行,不一定非要与对象,重载挂勾。有时采用对象,重载之类的反倒程序写起来麻烦,不如简单的实现。好比你不希望在容器类中进行判断,其实这种处理反倒是最直接,最直观的做法,自已与别人也更容易理解,速度也快。
Rcfeng 回复于:2005-09-24 22:36:40
嗯,是的,这种方法确实是比较没那么直观。但是我也尝试过自己使用二分法进行查找,但是速度始终不能太如意。
用C++进行同样的遍历可以实现很理想的速度,并且之前我也试过用C++的类和重载operator<实现(看了标准库中map部分的代码发现的办法)。
其实纯论使用的话,已经写了好几个可用的版本,用Cpp遍历一个,Cpp重载运算符一个,perl二分一个,php遍历一个,python遍历一个,python重载操作符一个,呵呵,是想深入学习和研究一下吧。在大量查询的情况下——4w个ip左右,基本上只有cpp的两个版本可以很快的完成(当然,如果能继续研究一下python的dic查找key方式,应该速度还是能令人满意的)。
刚开始学python,对这门语言感觉挺不错的,希望以后能与大家多多交流啦。
limodou 回复于:2005-09-24 23:37:35
如果是纯为了速度快,自然应该考虑算法的设计。如果只是简单地将ip段保存在一个list中,自然不会对运算速度的提高有任何的帮助。我想hash也并不能对无序的东西进行快速的索引。因此首先需要对存储的东西进行预处理,然后再进行检索才可以加快速度。如果只是顺序处理的话,那可以考虑使用swig或pyrex将检索处理转为扩展模块才行。
Rcfeng 回复于:2005-09-25 01:43:42
用来查询的ip库是经过排序的,所以formatip()后的序列是有序的,dic在加入key:value时是会按照hash进行一定的排序,所以强制对dic进行某种规则的sort必定会降低它的性能。正因为如此,对dic的get操作肯定是快于list(无序的数组)的index的。
所以我如果能够借用dic的查找算法,只是重载判断相等的操作符,那样的效果就是最好的了。
似乎对脚本语言处理速度的挑战不是很明智吧,但是研究一些深入的东西对学习还是有好处的。
西门吹风_CU 回复于:2007-02-16 13:56:13
学习了楼主这个帖子挺有收获的,对类内部的一些专用方法有了了解。虽然已经是个挺老的帖子了,但是俺还是尝试在楼主前面代码的基础上改动一下,增加了一个函数,修改了ip class,基本上实现了楼主的初衷。希望不会让各位大大见笑了:)
其实实现的方法也挺简单的,就是按照楼主的思路让同一子网内的ip都返回同一hash值。为了达到这个目的扩充了一下ip class,让它即可以接受作为boundary以low_ip, up_ip来初始化的实例,同时也可以接受以单个欲查找ip来初始化的实例。另外修改了__hash__函数,使得同一子网内的ip返回相同的hash值,其实就是把ip的第四段赋为0然后求formatip(ip)。
#!/usr/bin/python
def formatip(ip):
if is_ip(ip):
ipstr = map(int,(ip.split('.')))
return (((ipstr[0]*256+ipstr[1])*256+ipstr[2])*256+ipstr[3])
else:
return ip
def deformatip(ip):
ipstr = []
for i in range(4):
ipstr.append("%i"%(ip % 256))
ip /= 256
for i in range(2):
a = ipstr
ipstr = ipstr[-i-1]
ipstr[-i-1] = a
return ipstr
def is_ip(ip_str) :
"Check a string is a ip address or not"
parts = ip_str.split('.')
if len(parts) != 4 :
return False
for part in parts :
if not (part.isdigit() and int(part) <= 255 and int(part) >= 0):
return False
return True
class ip:
"A Ip class for finding user's area"
def __init__(self, *args):
if len(args) == 2:
self.low_ip = args[0]
self.up_ip = args[1]
self.ip = None
self.type = "bound"
if len(args) == 1:
self.ip = args[0]
self.low_ip = None
self.up_ip = None
self.type = "target"
def printip(self):
if self.type == "bound":
print "Low: %d; Up: %d" % (self.low_ip,self.up_ip)
else:
print "Target: %d" % self.ip
def __cmp__(self,ip):
if( hash(ip) == hash(self) ):
return 0
elif( hash(ip) < hash(self) ):
return -1
else: return 1
def __eq__(self,ip):
if( hash(ip) == hash(self) ):
return True
else: return False
def __ne__(self,ip):
if( hash(ip) < hash(self) or hash(ip) > hash(self) ):
return True
else: return False
def __hash__(self):
if self.type == "bound":
deip = deformatip(self.low_ip)
else:
deip = deformatip(self.ip)
deip[-1] = "0"
return(hash(formatip(".".join(deip))))
if( __name__ == "__main__"):
import sys
if(len(sys.argv) != 3):
print "Not current arguments: %d" % len(sys.argv)
sys.exit()
try:
datafile=open(sys.argv[1],"r")
infile=open(sys.argv[2],"r")
except IOError:
print "Open file error,exiting..."
sys.exit()
ip_map={}
for line in datafile.readlines():
onearea = map(formatip,line.split())
ip_map[ip(onearea[0],onearea[1])] = onearea[2]
for userip in infile.readlines():
oneinf = map(formatip, userip.split())
loc=ip_map.get(ip(oneinf[1]))
if( loc ):
print "Time:%s, Ip:%s, Area:%s" % (oneinf[0], oneinf[1], loc)
else: print "Time:%s, Ip:%s, Area:%s" % (oneinf[0], oneinf[1], "unknow")
另外有一点在我实际执行时发现,只修改__hash__使得同一子网内的ip实例返回同样的hash值,并不能实现楼主的初衷,即用类的实例作为字典的key。用ip_map[ip(oneinf[1])]来取value,python会出现异常,用ip_map.get(ip(oneinf[1]))来取,python返回None。只有同时修改了__cmp__, __eq__, __ne__才可以实现以实例作为key的要求。看起来好像以实例作为key是和hash值以及__cmp__同时相关的。
ghostwwl 回复于:2007-02-25 05:51:58
哇 刚才乱说了 没看清代码就乱说
昏 不过我正准备说 把ip都弄成10进制 比较方便些
[ 本帖最后由 ghostwwl 于 2007-2-25 06:51 编辑 ]
|