Managing types is normally simple in python, since it does typing as late as possible during runtime. Sometimes the issues of type still rears it's head, especially among programmers used to the "type at compile time" variants.
You want to resist doing a naive approach like type(obj)==str, since you then ignore subclasses.
What this recipe does is make a distinction between objects that _are_ are certain type or it's subclass and objects that act good enough. Most of the time the difference will not matter. If it is a certain python type or in some cases like with a sequence, possible among a few python types, it returns a 1, if it is good enough it returns a -1, and finally it returns zero if it is not good enough.
Good enough is enabled by checking for attributes and callables instead of explicit type and sometimes by checking for success of certain actions. In cases where it can be good enough, you simply check for just a true value which both 1 and -1 will evaluate to.
I put in typical checks for many things ranging from generators to lists to file handles.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | from types import *
def check_type(obj,atts=[],callables=[]):
got_atts=True
for att in atts:
if not hasattr(obj,att):
got_atts=False;break
got_callables=True
for call in callables:
if not hasattr(obj,call):
got_callables=False;break
the_attr=getattr(obj,call)
if not callable(the_attr):
got_callables=False;break
if got_atts and got_callables: return -1
return 0
def is_iter(obj):
if isinstance(obj,ListType): return 1
if isinstance(obj,TupleType): return 1
if isinstance(obj,DictType): return 1
if isinstance(obj,FileType): return 1
try:
iter(obj)
return -1
except TypeError:
return 0
def is_gen(obj):
if isinstance(obj,GeneratorType): return 1
return 0
def is_seq(obj):
if isinstance(obj,ListType): return 1
if isinstance(obj,TupleType): return 1
if is_iter(obj):
try:
obj[0:0]
return -1
except TypeError:
pass
return 0
def is_mapping(obj):
if isinstance(obj,DictType): return 1
if is_iter(obj):
return check_type(obj,callables=['iteritems','has_key'])
return 0
def is_list(obj):
if isinstance(obj,ListType): return 1
if is_seq(obj):
if check_type(obj,callables=['append','extend','pop']): return -1
return 0
def is_str(obj):
if isinstance(obj, basestring): return 1
if is_iter(obj):
if check_type(obj,callables=['index','count','replace']): return -1
return 0
def is_file(obj):
if isinstance(obj,FileType): return 1
if check_type(obj,callables=['read','close']): return -1
return 0
def check_all(obj):
result=[ str(i) for i in (is_iter(obj),is_gen(obj),is_seq(obj),is_list(obj),is_str(obj),is_mapping(obj),is_file(obj))]
return '\t'.join(result)
#####################examples
print '\t'+'\t'.join(['iter','gen','seq','list','str','dict','file'])
print 'str\t',check_all('str')
print '(1,)\t',check_all((1,))
print '[]\t',check_all([])
print '{}\t',check_all({})
f=open('tmp.txt','w')
print 'file\t',check_all(f)
import cStringIO
cstr=cStringIO.StringIO()
print 'cstrio\t',check_all(cstr)
gen=(i for i in (1,2,3))
print 'gen\t',check_all(gen)
def test(): yield 1
print 'test()\t',check_all(test())
class fdict:
def iteritems(self): pass
def has_key(self): pass
print 'fdict\t',check_all(fdict)
|
One problem in python is if you are too strict about typing is that you short-circuit some of python's power. A lot of times what matters is that the object acts like a dictionary not that it _is_ a python dictionary. This enables people to make other interesting objects that you can still use. So, I have a function called check_type that checks for callables and attributes that is necessary for a type to be good enough, in cases where it is not that type.
In the example below, compare cstrio to file. The code gives you a -1 for it, which means it is not a file but acts like one (which would be true with cStringIO). In a case where you actually want something written to the filesystem in a typical manner with no side-effects, -1 may not be good enough. In that case, check for 1.
If you look at a str, notice how I didn't put it's explicit check in for sequence, so it got a -1 instead of a 1. In other words, even if there are other types I forget about or do not know about, using the good enough idea will save me, since a string does act like a sequence.
The class fdict is a fake dictionary that failed everything. This is because to act like a dictionary you have to be iterable. My fake dict has the right methods but is not iterable.
Here are results of the testing (I know the formatting isn't perfect.)
________iter____gen_____seq_____list____str_____dict____file
str_____-1______0______-1_______0_______1_______0_______0 (1,)_____1______0_______1_______0_______0_______0_______0 []_______1______0_______1_______1_______0_______0_______0 {}_______1______0_______0_______0_______0_______1_______0 file_____1______0_______0_______0_______0_______0_______1 cstrio__-1______0_______0_______0_______0_______0_______-1 gen_____-1______1_______0_______0_______0_______0_______0 test()__-1______1_______0_______0_______0_______0_______0 fdict____0______0_______0_______0_______0_______0_______0
You can reach me at pyguy2 on yahoo.
callable becoming obsolete. The is callable builtin is planned on becoming obsoleted in Python 3000.
Also, why should I care if an object has a callable called index? I can just try and call it, and if it isn't an exception will be called for me?
callable obsolete? I thought operator.isCallable was deprecated. I'll have to check about callable.
With regards to index in string, you can put in there whatever you find is sufficient to be stringlike for your uses. I happened to use index as one of the requirements. If it reaches that point it is not am instance of a native native python string, so, how string-like you want it to be is up to you.
Use hasatter(obj,'__iter__') instead of try: iter(obj) except: IMHO it is better to use hasatter(obj,'__iter__') for checking if an object is meant to be iterable. The try: iter(obj) approach can mask errors in the iterator, because an invalid __iter__() method will also raise TypeError.
Something that defines an __iter__() method intends to be iterable. If that __iter__() method has a bug that triggers an exception, you want that exception to propogate, not be caught and turned into "this is not iterable".
Also hasatter() is very lightweight. Instantiating an iterator instance might not be...