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
Related
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
I am writing a subclass of dict that maps from string keys to values of arbitrary types. If a key is a regex, it is stored and queried separately.
class RegexDict(dict):
def __init__(self):
super().__init__() # non-regex keys in the parent class
self.regex_dict = {} # regex keys in the child class
def __getitem__(self, key):
try:
return super().__getitem__(key)
except KeyError:
for x in self.regex_dict:
if re.fullmatch(x, key):
return self.regex_dict[x]
raise KeyError(key)
def __setitem__(self, key, value):
key, is_regex = key
if is_regex:
self.regex_dict[key] = value
else:
super().__setitem__(key, value)
Because this class will be used by other libraries (which is why I have to use inheritance), I want to make sure that an error is raised when non-overridden methods in the base class are called. How should I do this?
You should inherit from collections.abc.MutableMapping instead of dict.
It will fill in the gaps automatically, and also let you know which things you have to implement.
In addition to __getitem__ and __setitem__, you’ll also have to implement __delitem__, __iter__ and __len_. If you can’t implement those reasonably, you can raise e.g. NotImplementedError from them (even though that will limit the use of your class a lot).
This will have the advantage that all dict methods which only need __getitem__ and __setitem__ (+ what you implement) internally will work out of the box.
Don't subclass. To convince third-party libs that your object is a dict, set __class__ attribute.
class RegexDict():
__class__ = dict
def __init__(self):
self.non_regex_dict = {}
self.regex_dict = {}
def __getitem__(self, key):
try:
return self.non_regex_dict[key]
except KeyError:
for x in self.regex_dict:
if re.fullmatch(x, key):
return self.regex_dict[x]
raise KeyError(key)
def __setitem__(self, key, value):
key, is_regex = key
if is_regex:
self.regex_dict[key] = value
else:
self.non_regex_dict[key] = value
rd = RegexDict()
print(isinstance(rd, dict))
rd.clear()
output:
True
Traceback (most recent call last):
File "libo.py", line 30, in <module>
rd.clear()
AttributeError: 'RegexDict' object has no attribute 'clear'
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.
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.
I've already looked at this question: Python iterators – how to dynamically assign self.next within a new style class?
but this doesn't help me because I want to iterate of an attribute of the error which is a list (ie, already iterable) without having to use the attribute explicitly. I'm looking to do this:
class SCE(Exception):
"""
An error while performing SCE functions.
"""
def __init__(self, value=None):
"""
Message: A string message or an iterable of strings.
"""
if value is None:
self._values = ['A general SCE error has occured.']
elif isinstance(value, str):
self._values = [value]
else:
self._values = list(value)
def __iter__(self):
return self._values
def __repr__(self):
return repr(self._values)
However, in the shell I get this:
try:
raise CSE(['error one', 'error two'])
except CSE, e:
for i in e:
print(i)
Traceback (most recent call last):
File "(stdin)", line 1, in (module)
TypeError: iter() returned non-iterator of type 'list'
I know I could remove the _ from _values and then iterate over e.values but I don't want to do that as it exposes the implementation of my Exception class.
The __iter__ method should return an iterator object, but you are returning a list object. Use
def __iter__(self):
return iter(self._values)
instead to fix this. From the documentation for object.__iter__ (my highlighting):
This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container.
def __iter__(self):
return iter(self._values)
Or a more generic:
def __iter__(self):
for x in self._values:
yield x
__iter__ needs to return an iterator, not a list.
Try this:
def __iter__(self):
return iter(self._values)
You could also do:
def __iter__(self):
for val in self._values:
yield val
But I can't really think of a reason you'd need to do that instead of using iter()