python: method depends on whether param is int or string - python

Is there a more pythonic way to write __getitem__ than the following? The issue is checking type and doing different things depending on the type of the parameter in the call.
class This():
def __init__(self, name, value):
self.name, self.value = name, value
class That():
def __init__(self):
self.this_list = []
def add_this(self, this):
self.this_list.append(this)
def __getitem__(self, x):
if isinstance(x, int):
return self.this_list[x] # could wrap in try/except for error checking
elif isinstance(x, str):
for this in self.this_list:
if this.name == x:
return this
return None
a = This('a', 1)
b = This('b', 2)
c = That()
c.add_this(a)
c.add_this(b)
print c[1].name
print c['a'].name

There are quite a few options, but I think there is not one best choice. It depends on your use case and preferences. Just to give you a few hints:
Do you really have to store the data in a list? In your example you could use a dictionary and insert the object twice: Once using the integer as key and once using the string as a key. That would make your __getitem__ quite simple. ;-)
Another option would be to make your interface more explicit and use byInt/byString methods. You should choose better names of course.
If you give more details about what you really want to do, I could propose more alternatives.

You are almost always better off testing the behavior of the kind of item you want rather than explicitly testing for type. In your case, I'd simply try to get the desired item by index first and catch TypeError to check by name.
def __getitem__(self, key):
try:
return self.this_list[key]
except TypeError:
try:
return next(item for item in self.this_list if item.name == key)
except StopIteration:
raise KeyError("key `%s` not found" % key)
Note that this will automatically work with slices too, since in this case the key will be a slice object and that will work fine with the [...] notation.
You should probably be using a dict rather than a list inside your class, though, rather than searching a list for an object attribute. Exceptions would be if you really need slicing or if the names can be changed by code outside your class.
Another (perhaps slightly unconventional) possibility is to implement the special method __eq__() on your This class, allowing it to be compared to a string, so that if the class's name attribute is (say) "Jerry", then This("Jerry", 0) == "Jerry". Then you don't actually need the container class and can just use a regular list:
class This(object):
def __init__(self, name, value):
self.name, self.value = name, value
def __eq__(self, other):
return self.name == other
thislist = [This("Jerry", 42), This("Amy", 36)]
"Jerry" in thislist # True
thislist.index("Amy") # 1
The syntax for accessing an item by name is still a little hairy:
thislist[thislist.index("Amy")]
But you can simply subclass list and combine this with my previous suggestion, which becomes simpler and more generic, since it works with any object that knows how to compare itself to whatever kind of key you're using:
class That(list):
def __getitem__(self, key):
try:
return list.__getitem__(self, key)
except TypeError:
return list.__getitem__(self, self.index(key))
thislist = That([This("Jerry", 42), This("Amy", 36)])
thislist["Amy"].value # 36

Is there a more pythonic way to write getitem in the following?
Only slightly. __getitem__ is used by both sequences, where int's and slice's are used, and by mappings, where pretty much anything can be used. It looks like you are implementing both sequence-type and mapping-type interfaces, so you're stuck with checking type.
Missing two things:
support for slices (but only put it in if you want your That to support it)
raising an exception for failure (returning None in this case is not pythonic)
Here's an updated __getitem__:
def __getitem__(self, x):
if isinstance(x, int):
return self.this_list[x]
elif isinstance(x, slice):
return self.this_list[slice]
elif isinstance(x, str):
for this in self.this_list:
if this.name == x:
return this
return None
raise KeyError("invalid key: %r" % x)
At this point you have two possible exceptions being raised
IndexError (if x is outside the range of this_list)
KeyError (if the name is not found, or something besides str or int was passed in)
This may be fine for you, or you might want to create a custom Exception that gets returned in all cases:
class LookupError(Exception):
"x is neither int nor str, or no matching This instance found"
Here's the updated code (Python 2.x):
class LookupError(IndexError, KeyError):
"x is neither int nor str, or no matching This instance found"
class This():
def __init__(self, name, value):
self.name, self.value = name, value
class That(object):
def __init__(self):
self.this_list = []
def add_this(self, this):
self.this_list.append(this)
def __getitem__(self, x):
try:
if isinstance(x, int):
return self.this_list[x]
elif isinstance(x, slice):
return self.this_list[slice]
elif isinstance(x, str):
for this in self.this_list:
if this.name == x:
return this
raise KeyError("invalid key: %r" % x)
except (IndexError, KeyError), err:
raise LookupError(err.message)
a = This('a', 1)
b = This('b', 2)
c = That()
c.add_this(a)
c.add_this(b)
print c[1].name
print c['a'].name
try:
print c[2.0]
except LookupError, e:
print e
try:
print c['c']
except LookupError, e:
print e

You can define two private methods __getitem_int() and __getitem_str(). Then you can use getattr() to get handle to proper method depending of type(x).__name__ and call type-specific method.
See how KantGenerator.parse() is implemented in dive into python parsing xml example.

Related

How do I make a subclass of "dict" that returns a fallback value when it can't find a value for a key?

I want to create SafeDict, a subclass of dict.
When asked to retrieve a value for a non existing key, SafeDict should return a fallback value.
(I know there are other ways to do this, but I'm practicing my OOP skills.)
Right now I have:
class SafeDict(dict):
def __getitem__(self, key):
try:
super().__getitem__(key)
except KeyError:
return "Beetle"
a = {"Ford": "Mustang"}
b = SafeDict({"Ferrari": "Enzo"})
print(a["Ford"]) # This prints "Mustang" (good)
#print(a["Aston"]) # This raises KeyError (good)
print(b["Ferrari"]) # This should print "Enzo", but it prints "None" (bad)
print(b["Aston"]) # This prints "Beetle" (good)
Maybe print(b["Ferrari"]) prints None beacuse super() runs the __getitem__ method on the list superclass, and that class has no dictionary with "Ferrari": "Enzo" in it?
I tried to remove the super(), and use self, but I ran into recursion problems.
Help?
You forgot to return the super() value:
class SafeDict(dict):
def __getitem__(self, key):
try:
return super().__getitem__(key)
except KeyError:
return "Beetle"
When a method has no explicit return statement, the function implicitly returns None

Avoiding recursion when setting exceptions

I'm trying to do something relatively simple - to take the OrderedDict class and give it the ability to look up values based on integers or slices. So if I have the following
from collections import OrderedDict
test_dict = OrderedDict()
test_dict['a'] = 'value a'
test_dict['b'] = 'value b'
test_dict['c'] = 'value c'
test_dict[:2]
It would ideally give the result
['value a','value b']
To do this, I'm trying to subclass OrderedDict and then over-ride its getitem method. What I have is the following:
class indexed_dict(OrderedDict):
def __init__(self):
pass
def __getitem__(self, key):
try:
return list(self)[key]
except TypeError: # Because we've entered a number instead of a key
return self[key]
The trouble with this is that if you try to use it normally - say, test_indexed_dict['a'] - the interpreter starts recursing away, presumably because the except clause asks it to run getitem again. Is there a better method of error handling which doesn't create this problem?
Thanks in advance for any help.
In addition to #Aran_Fey's answer, you need to check values in first try , so function would be:
def __getitem__(self, key):
try:
return list(self.values())[key]
except TypeError:
return super().__getitem__(key)
And whole class:
class IndexedDict(OrderedDict):
def __init__(self):
pass
def __getitem__(self, key):
try:
return list(self.values())[key]
except TypeError:
return super().__getitem__(key)
Nothing can be added to #Netwave's answer.
When you do self[key], you're calling your own __getitem__ method again. That causes endless recursion. What you want to do is to call your parent class's __getitem__ method:
def __getitem__(self, key):
try:
return list(self)[key]
except TypeError: # Because we've entered a number instead of a key
return super().__getitem__(key)
You can check the type of key, and also just return the slice over the values of the dictionary:
from collections import OrderedDict
class IndexedDict(OrderedDict):
def __init__(self):
pass
def __getitem__(self, key):
if type(key) is slice:
return list(self.values())[key]
else:
return super().__getitem__(key)
Here you have a live example

Deferring a computation in custom object until data available

I have a custom class like the below. The idea, as the naming suggests, is that I want to evaluate a token stream in a parser-type tool. Once a bunch of constructs have been parsed out and put into data structures, certain sequences of tokens will evaluate to an int, but when the data structures aren't available yet, a function just returns None instead. This single complex data structure 'constructs' gets passed around pretty much everywhere in the program.
class Toks(list):
def __init__(self, seq, constructs=Constructs()):
list.__init__(self, seq)
self.constructs = constructs
#property
def as_value(self):
val = tokens_as_value(self, self.constructs)
return val if val is not None else self
At points in the code, I want to assign this maybe-computable value to a name, e.g.:
mything.val = Toks(tokens[start:end], constructs).as_value
Well, this gives mything.val either an actual int value or a funny thing that allows us to compute a value later. But this requires a later pass to actually perform the computation, similar to:
if not isinstance(mything.val, int):
mything.val = mything.val.as_value
As it happens, I can do this in my program. However, what I'd really like to happen is to avoid the second pass altogether, and just have access to the property perform the computation and give the computed value if it's computable at that point (and perhaps evaluate to some sentinal if it's not possible to compute).
Any ideas?
To clarify: Depending on the case I get "value" differently; actual code is more like:
if tok.type == 'NUMBER':
mything.val = tok.value # A simple integer value
else:
mything.val = Toks(tokens[start:end], constructs).as_value
There are additional cases, sometimes I know I know the actual value early, and sometimes I'm not sure if I'll only know it later.
I realize I can defer calling (a bit more compactly than #dana suggests) with:
return val if val is not None else lambda: self.as_value
However, that makes later access inconsistent between mything.val and mything.val(), so I'd still have to guard it with an if to see which style to use. It's the same inconvenience whether I need to fall back to mything.val.as_value or to mything.val() after the type check.
You could easily do something like:
class NaiveLazy(object):
def __init__(self, ctor):
self.ctor = ctor
self._value = None
#property
def value(self):
if self._value is None:
self._value = ctor()
return self._value
mything = NaiveLazy(lambda: time.sleep(5) and 10)
And then always use mything.value (example to demonstrate evaluation):
print mything.value # will wait 5 seconds and print 10
print mything.value # will print 10
I've seen some utility libraries create a special object for undefined in case ctor returns None. If you eventually want to extend your code beyond ints, you should think about that:
class Undefined(object): pass
UNDEFINED = Undefined()
#...
self._value = UNDEFINED
#...
if self._value is UNDEFINED: self._value = ctor()
For your example specifically:
def toks_ctor(seq, constructs=Constructs()):
return lambda l=list(seq): tokens_as_value(l, constructs) or UNDEFINED
mything = NaiveLazy(toks_ctor(tokens[start:end], constructs))
If you're using Python3.2+, consider a Future object. This tool lets you run any number of calculations in the background. You can wait for a single future to be completed, and use its value. Or, you can "stream" the results one at a time as they're completed.
You could return a callable object from as_value, which would allow you automatically check for the real return value automatically. The one drawback is you'd need to use mything.val() instead of mything.val:
def tokens_as_value(toks, constructs):
if constructs.constructed:
return "some value"
else:
return None
class Constructs(object):
def __init__(self):
self.constructed = False
class Toks(list):
def __init__(self, seq, constructs=Constructs()):
list.__init__(self, seq)
self.constructs = constructs
#property
def as_value(self):
return FutureVal(tokens_as_value, self, self.constructs)
class FutureVal(object):
def __init__(self, func, *args, **kwargs):
self.func = func
self._val = None
self.args = args
self.kwargs = kwargs
def __call__(self):
if self._val is None:
self._val = self.func(*self.args, **self.kwargs)
return self._val
Just for the purposes of the example, Constructs just contains a boolean that indicates whether or not a real value should be returned from tokens_as_value.
Usage:
>>> t = test.Toks([])
>>> z = t.as_value
>>> z
<test.FutureVal object at 0x7f7292c96150>
>>> print(z())
None
>>> t.constructs.constructed = True
>>> print(z())
our value

How to handle python exception inside if statement

For example I got this if statement:
if user.address.streetname == 'Target':
pass
elif:
(...)
else:
(...)
But not all users have enough money to have an address so it could raise an exception
In my case a django DoesNotExist exception. In this case it should assume false.
How to handle exception in this place without breaking if elif else flow?
If user.address is a model instance, you can do
if user and user.address and user.address.streetname and user.address.streetname == 'Target':
#Do something/
Or, you can also do:
address = getattr(user, 'address', None) if user else None
if address and getattr(address, 'streetname', '') == 'Target':
#do something
Use duck typing and create a sentinel object that is guaranteed to have a non-matching streetname attribute to use in place of an unavailable user.address.
poor_user_address = type('', (), {'streetname': None})()
if getattr(user, 'address', poor_user_address).streetname == "Target":
...
The call to type creates a minimal class with a class variable streetname; the rest of the details of the class are irrelevant. With duck typing, it doesn't matter that poor_user_address is an instance of a different class, as long as it exhibits the same behavior. In this case, the only expected behavior is to have a streetname attribute that can be compared to "Target".
Here's another stackoverflow question that answers this
The relevant part would be: hasattr(user, 'address')
If you add that to your if before accessing the property you can maintain the if/else flow.
try:
streetname = user.address.streetname
except DoesNotExist:
streetname = None
# or:
# streetname = NonexistenceSentinel()
if streetname == 'Target':
pass
elif:
...
else:
...
But probably what you are really looking for is some syntactic sugar to allow you to not put this in everywhere. Here's a recipe that lets you do that:
# 'Stupid' object that just returns itself.
# Any attribute will just return itself.
class SentinelObject(object):
__slots__ = []
def __init__(self):
pass
def __getattr__(self, key):
return self
def __nonzero__(self):
return False
def delegate_specials(specials):
specialnames = ['__%s__'%s for s in specials.split()]
def wrapit(cls, method):
return lambda self, *args, **kwargs: getattr(self._original_obj, method)(*args, **kwargs)
def dowrap(cls):
for n in specialnames:
setattr(cls, n,
wrapit(cls, n))
return cls
return dowrap
#delegate_specials('getitem setitem iter add sub mul div repr str len')
class SafeLookupObject(object):
__slots__ = ['_original_obj', '_sentinel_default']
__original_names__ = ['_original_obj', '_sentinel_default']
def __init__(self, original_obj, sentinel_default=SentinelObject()):
self._original_obj = original_obj
self._sentinel_default = sentinel_default
def __getattr__(self, key):
if key in self.__original_names__:
return object.__getattr__(self, key)
else:
try:
val = getattr(self._original_obj, key)
if callable(val):
return val
else:
return SafeLookupObject(val, self._sentinel_default)
except AttributeError:
return self._sentinel_default
def __setattr__(self, key, value):
if key in self.__original_names__:
return object.__setattr__(self, key, value)
else:
return setattr(self._original, key, value)
May not be perfect, looks OK at a first pass.
What this does: You pass in an original object, and a default val. The default val is a special SentinelObject (more on that in a minute). Only for getattr, if it doesn't exist, it returns the sentinel value. If it does exist, it checks to see if it's callable or not. If it's callable (i.e. a function), it returns it directly. If not, it wraps it in a SafeLookupObject and returns it.
The intention is that if you want to lookup x.y.z, you can just wrap x in the SafeLookupObject, and then x.y will automatically be wrapped as well, all the way down, so if it fails anywhere in the list, that value will be replaced with the sentinel object.
Therefore the special SentinelObject, which returns itself for whatever attribute you pass in. This with the above makes it fully recursive.
I.e. let's say you look up a.b.c.d with a as safe. a.b is OK, but then c does not exist in b. With a default of None, a.b.c returns None, but then a.b.c.d raises an exception.
If you use the SentinelObject as the default, then a.b.c instead returns a SentinelObject which both is boolean False, and can be matched against to determine a non-existent attribute. a.b.c.d also returns the same SentinelObject, and so it's now completely safe.

Python subclass tuple object with ability to reinstantiate self internally

I understand the concept of mutable v. immutable objects in Python, no problem. While any immutable object's intrinsic value cannot be modified directly, any instance of an immutable object can be reinstantiated with different values. What I would like to do is build an internal function on a subclass of tuple that can in a controlled fashion, reassign it's own value. This could be basic functionality that I just can't seem to find and would appreciate any assistance.
For example, here is what I'd like to be able to do, but this obviously doesn't work.
class myTuple(tuple):
def __new__(self):
initialValue = [1, 2, 3]
return super(myTuple, self).__new__(self, initialValue)
def resetMyself(self):
newValue = [4, 5, 6]
self = tuple(newValue)
With the following results...
>>> foo = myTuple()
>>> print foo
(1, 2, 3)
>>> foo.resetMyself()
>>> print foo
(4, 5, 6)
From reading a larger number of responses to questions like this on this site, I know some of you may have the tendency to respond with "Why would you want to do this?" but let's save the response space with more direct answers, including possibly "You cannot do that no way, no how," if that's really the case.
Thanks very much all!
EDIT, THANKS FOR THE ANSWER BELOW, HERE IS WHAT I ENDED UP WITH...
class semiImmutableList(list):
def __setitem__(self, *args):
raise TypeError("'semiImmutableList' object doesn't support item assignment")
__setslice__ = __setitem__
def __delitem__(self, *args):
raise TypeError("'semiImmutableList' object doesn't support item deletion")
__delslice__ = __delitem__
def append(self, *args):
raise AttributeError("'semiImmutableList' object has no attribute 'append'")
def extend(self, *args):
raise AttributeError("'semiImmutableList' object has no attribute 'extend'")
def insert(self, *args):
raise AttributeError("'semiImmutableList' object has no attribute 'insert'")
def remove(self, *args):
raise AttributeError("'semiImmutableList' object has no attribute 'remove'")
def pop(self, *args):
raise AttributeError("'semiImmutableList' object has no attribute 'pop'")
def __init__(self):
x = [1, 2, 3]
super(semiImmutableList, self).__init__(x)
def resetMyself(self):
super(semiImmutableList,self).append(5)
Any improvements/adjustments to the above that you can see please post. Seems like the duplication of AttributeError raises could be combined?
If you want a mutable tuple, use a list.
edit:
try this
class FrankenList(object):
def __init__(self, init=None):
self.__data = init or []
def __getitem__(self, key):
return self.__data[key]
def __repr__(self):
return repr(self.__data)
def __str__(self):
return str(self.__data)
Pretty easy, all you have to do is to wrap a list.
class ImmutableList(object):
def __init__(self, *args):
self.__values = args; # internally we store the values in a list
# make imuList[0] = 2 raise an error, just like a tuple would
def __setitem__(self, index, value):
raise TypeError('ImmutableList does not support item assignment')
# del imuList[0] should also raise
def __delitem__(self, index, value):
raise TypeError('ImmutableList does not support item deletion')**
# make our imuList indexable, also catch the normal index error and raise one
# that tells that this is an immutable list, will make it easier to debug :)
def __getitem__(self, index):
try:
return self.__values[index]
except IndexError:
raise IndexError('ImmutableList index out of range')
# the usual stuff
def __repr__(self):
return repr(self.__values)
def __str__(self):
return str(self.__values)
# create a new imulist
e = ImmutableList(1, 2, 3, 4)
# works!
print e[0]
# raises an error
e[0] = 5
# raises another error
print e[9]
Now all you have to do is to modify self._values inside the class. One last advise, it's still possible to mess with self._values from the outside, that because Python doesn't support private members.
You can take further measures against the manipulation of __values by subclassing from list directly, but that's more work and one can still fiddle around with the values by using list.__setitem__(imListInstance, 0, 5) and the like.

Categories