Python dictionary key error only in one specific case - python

I have a class that stores a list of lists in a matrix style, and it can just be indexed like [x,y].
Right now I have these set:
test_dict = {1:"cow",2:"horse"}
randommap = mapArray(None, (20,20))
random map is just filled with lists of 1s. So any index will return a 1. But here is where I get lost, maybe because of a misunderstanding about how dictionaries work:
test_dict[1]
That obviously gives back "cow"
and
randommap[1,1] #or any two x,y values up to 20 for that matter
gives me a value of 1.
But how come this gives me a key error:
test_dict[randommap[1,1]]
In isolation, indexing randommap there gives me a value of 1, so shouldn't that 1 be supplied as an index for test_dict, thus returning me "cow"?
Update:
These are the two methods I presume are causing the issue. It appears that they are returning strings instead of integers, but I don't know If I quite understand the difference between the two.
def __str__(self):
return str(self.mapdata)
def __repr__(self):
return str(self.mapdata)
Here is the overloaded __getitem__ method:
def __getitem__(self, (x,y)):
#Just reverses it so it works intuitively and the array is
# indexed simply like map[x,y] instead of map[y][x]
return mapArray(self.mapdata[y][x])
Sorry the formatting seems to have been messed up a bit.

There is a difference between 1 (and integer), "1" (a string), and any custom class whose __repr__() method returns the string "1" when called. They will all be printed as 1 in the console, but are not equivalent for dict look-ups.
You need to check that type(randommap[1, 1]) is indeed an int.
Update: Your __getitem__ method doesn't return integers, it returns a new instance of your mapArray class. Did you mean to return just the values themselves? E.g.:
def __getitem__(self, (x,y)):
#Just reverses it so it works intuitively and the array is
# indexed simply like map[x,y] instead of map[y][x]
return self.mapdata[y][x]

Given the updated question, it seems that __getitem__ returns a new mapArray.
I think your overloaded __getitem__ should be something like
def __getitem__(self, (x,y)):
return self.mapdata[y][x]
instead, (assuming that the [y][x] order is intentional).

Related

What are the best practices for __repr__ with collection class Python?

I have a custom Python class which essentially encapsulate a list of some kind of object, and I'm wondering how I should implement its __repr__ function. I'm tempted to go with the following:
class MyCollection:
def __init__(self, objects = []):
self._objects = []
self._objects.extend(objects)
def __repr__(self):
return f"MyCollection({self._objects})"
This has the advantage of producing a valid Python output which fully describes the class instance. However, in my real-wold case, the object list can be rather large and each object may have a large repr by itself (they are arrays themselves).
What are the best practices in such situations? Accept that the repr might often be a very long string? Are there potential issues related to this (debugger UI, etc.)? Should I implement some kind of shortening scheme using semicolon? If so, is there a good/standard way to achieve this? Or should I skip listing the collection's content altogether?
The official documentation outlines this as how you should handle __repr__:
Called by the repr() built-in function to compute the “official”
string representation of an object. If at all possible, this should
look like a valid Python expression that could be used to recreate an
object with the same value (given an appropriate environment). If this
is not possible, a string of the form <...some useful description...>
should be returned. The return value must be a string object. If a
class defines __repr__() but not __str__(), then __repr__() is also
used when an “informal” string representation of instances of that
class is required.
This is typically used for debugging, so it is important that the
representation is information-rich and unambiguous.
Python 3 __repr__ Docs
Lists, strings, sets, tuples and dictionaries all print out the entirety of their collection in their __repr__ method.
Your current code looks to perfectly follow the example of what the documentation suggests. Though I would suggest changing your __init__ method so it looks more like this:
class MyCollection:
def __init__(self, objects=None):
if objects is None:
objects = []
self._objects = objects
def __repr__(self):
return f"MyCollection({self._objects})"
You generally want to avoid using mutable objects as default arguments. Technically because of the way your method is implemented using extend (which makes a copy of the list), it will still work perfectly fine, but Python's documentation still suggests you avoid this.
It is good programming practice to not use mutable objects as default
values. Instead, use None as the default value and inside the
function, check if the parameter is None and create a new
list/dictionary/whatever if it is.
https://docs.python.org/3/faq/programming.html#why-are-default-values-shared-between-objects
If you're interested in how another library handles it differently, the repr for Numpy arrays only shows the first three items and the last three items when the array length is greater than 1,000. It also formats the items so they all use the same amount of space (In the example below, 1000 takes up four spaces so 0 has to be padded with three more spaces to match).
>>> repr(np.array([i for i in range(1001)]))
'array([ 0, 1, 2, ..., 998, 999, 1000])'
To mimic this numpy array style you could implement a __repr__ method like this in your class:
class MyCollection:
def __init__(self, objects=None):
if objects is None:
objects = []
self._objects = objects
def __repr__(self):
# If length is less than 1,000 return the full list.
if len(self._objects) < 1000:
return f"MyCollection({self._objects})"
else:
# Get the first and last three items
items_to_display = self._objects[:3] + self._objects[-3:]
# Find the which item has the longest repr
max_length_repr = max(items_to_display, key=lambda x: len(repr(x)))
# Get the length of the item with the longest repr
padding = len(repr(max_length_repr))
# Create a list of the reprs of each item and apply the padding
values = [repr(item).rjust(padding) for item in items_to_display]
# Insert the '...' inbetween the 3rd and 4th item
values.insert(3, '...')
# Convert the list to a string joined by commas
array_as_string = ', '.join(values)
return f"MyCollection([{array_as_string}])"
>>> repr(MyCollection([1,2,3,4]))
'MyCollection([1, 2, 3, 4])'
>>> repr(MyCollection([i for i in range(1001)]))
'MyCollection([ 0, 1, 2, ..., 998, 999, 1000])'

Return a new tuple with combination of 2 parameters called by an function

In Python, I have a list say
t = ("ab","de","fg","jk")
Make it so that the function above appendI call returns a new tuple that is the combination of tup and value at the end of the new tuple.
I am getting a list at end but i need a tuple not an array.
Please anyone help me with this, as i new to PYTHON
code part is :
t = ("ab","de","fg","jk")
def append(tup, value):
return (tup+ value, value + tup)
append(*tuple)
After running above, i am getting append argument after * must be iterable not type
You can concatenate tuples. There is also a convention for expressing a one value tuple so the return could be as simple as: return tup + (value,)

Why does my equal method always return false?

Hi I just started learning classes in python and I'm trying to implement an array based list. This is my class and the init constructor.
class List:
def __init__(self,max_capacity=50):
self.array=build_array(max_capacity)
self.count=0
However, I wrote a method equals that returns true if the list equals another. However, it always return false. And yes my append method is working.
def __eq__(self,other):
result=False
if self.array==other:
result=True
else:
result=False
return result
This is how I tested it but it return false?
a_list=List()
b_list=[3,2,1]
a_list.append(3)
a_list.append(2)
a_list.append(1)
print(a_list==b_list)
Any help would be appreciated!
EDIT:
After all the helpful suggestions, I figured out I have to iterate through other and a_list and check the elements.
__eq__, for any class, should handle three cases:
self and other are the same object
self and other are compatible instances (up to duck-typing: they don't need to be instances of the same class, but should support the same interface as necessary)
self and other are not comparable.
Keeping these three points in mind, define __eq__ as
def __eq__(self, other):
if self is other:
return True
try:
return self.array == other.array
except AttributeError:
# other doesn't have an array attribute,
# meaning they can't be equal
return False
Note this assumes that a List instance should compare as equal to another object as long as both objects have equal array attributes (whatever that happens to mean). If that isn't what you want, you'll have to be more specific in your question.
One final option is to fall back to other == self to see if type of other knows how to compare itself to your List class. Equality should be symmetric, so self == other and other == self should produce the same value if, indeed, the two values can be compared for equality.
except AttributeError:
return other == self
Of course, you need to be careful that this doesn't lead to an infinite loop of List and type(other) repeatedly deferring to the other.
You are comparing the array embedded in your instance (self.array) to the entirety of the other object. This will not work well.
You need to compare self.array to other.array and/or convert both objects to the same type before comparing. You probably also need to specify what it means to compare two arrays (i.e., you want a single boolean value that indicates whether all elements are equal, not an array of boolean values for each element).
For the code below, I assume you are using a numpy ndarray for self.array. If not, you could write your own array_equal that will convert other to an array, then compare the lengths of the arrays, then return (self.array==other_as_array).all().
If you want to test for strict equality between the objects (same types, same values), you could use this:
from numpy import array_equal
import numpy as np
class List
...
def __eq__(self, other):
return isinstance(other, List) and array_equal(self.array, other.array)
If you just want to check for equality of the items in the list, regardless of the object type, then you could do this:
def __eq__(self, other):
if isinstance(other, List):
return array_equal(array, other.array)
else:
return array_equal(self.array, other)

Organize values of nested tuples in a single tuple

I'm extracting features from a specific class of objects I have and decided to built a method that extracts all features at once, i.e. call all feature extraction methods and return them in a tuple, as shown below.
def extractFeatures(self):
if self.getLength()<=10:
return ()
else:
return (self.getMean(), # a number
self.getStd(), # a number
self.getSkew(), # a number
self.getKurt(), # a number
# Many other methods here, such as:
self.getACF(), # which returns a TUPLE of numbers...
)
Nevertheless, I have some methods returning tuples with numbers instead of individual numbers, and since I'm still doing some tests and varying the length in each one of these tuples, hard typing self.getACF()[0], self.getACF()[1], self.getACF()[2], ... is not a good idea.
Is there a pythonic way of getting these values already "unpacked" so that I can return a tuple of only numbers instead of numbers and maybe nested tuples of indefinite size?
You could build a list of the values to return, then convert to a tuple at the end. This lets you use append for single values and extend for tuples:
def extractFeatures(self):
if self.getLength() > 10:
out = [self.getMean(), self.getStd(), self.getSkew()]
out.append(self.getKurt()] # single value
out.extend(self.getACF()) # multiple values
return tuple(out)
Note that this will implicitly return None if self.getLength() is 10 or less.
However, bear in mind that your calling function now needs to know exactly what numbers are coming and in what order. An alternative in this case is to return a dictionary:
return {'mean': self.getMean(), ... 'ACF': self.getACF()}
Now the calling function can easily access the features required by key, and you can pass these as keyword arguments to other functions with dictionary unpacking:
def func_uses_mean_and_std(mean=None, std=None, **kwargs):
...
features = instance.extractFeatures()
result = func_uses_mean_and_std(**features)

Python lists/arrays: disable negative indexing wrap-around in slices

While I find the negative number wraparound (i.e. A[-2] indexing the second-to-last element) extremely useful in many cases, when it happens inside a slice it is usually more of an annoyance than a helpful feature, and I often wish for a way to disable that particular behaviour.
Here is a canned 2D example below, but I have had the same peeve a few times with other data structures and in other numbers of dimensions.
import numpy as np
A = np.random.randint(0, 2, (5, 10))
def foo(i, j, r=2):
'''sum of neighbours within r steps of A[i,j]'''
return A[i-r:i+r+1, j-r:j+r+1].sum()
In the slice above I would rather that any negative number to the slice would be treated the same as None is, rather than wrapping to the other end of the array.
Because of the wrapping, the otherwise nice implementation above gives incorrect results at boundary conditions and requires some sort of patch like:
def ugly_foo(i, j, r=2):
def thing(n):
return None if n < 0 else n
return A[thing(i-r):i+r+1, thing(j-r):j+r+1].sum()
I have also tried zero-padding the array or list, but it is still inelegant (requires adjusting the lookup locations indices accordingly) and inefficient (requires copying the array).
Am I missing some standard trick or elegant solution for slicing like this? I noticed that python and numpy already handle the case where you specify too large a number nicely - that is, if the index is greater than the shape of the array it behaves the same as if it were None.
My guess is that you would have to create your own subclass wrapper around the desired objects and re-implement __getitem__() to convert negative keys to None, and then call the superclass __getitem__
Note, what I am suggesting is to subclass existing custom classes, but NOT builtins like list or dict. This is simply to make a utility around another class, not to confuse the normal expected operations of a list type. It would be something you would want to use within a certain context for a period of time until your operations are complete. It is best to avoid making a globally different change that will confuse users of your code.
Datamodel
object.getitem(self, key)
Called to implement evaluation of
self[key]. For sequence types, the accepted keys should be integers
and slice objects. Note that the special interpretation of negative
indexes (if the class wishes to emulate a sequence type) is up to the
getitem() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the
sequence (after any special interpretation of negative values),
IndexError should be raised. For mapping types, if key is missing (not
in the container), KeyError should be raised.
You could even create a wrapper that simply takes an instance as an arg, and just defers all __getitem__() calls to that private member, while converting the key, for cases where you can't or don't want to subclass a type, and instead just want a utility wrapper for any sequence object.
Quick example of the latter suggestion:
class NoWrap(object):
def __init__(self, obj, default=None):
self._obj = obj
self._default = default
def __getitem__(self, key):
if isinstance(key, int):
if key < 0:
return self._default
return self._obj.__getitem__(key)
In [12]: x = range(-10,10)
In [13]: x_wrapped = NoWrap(x)
In [14]: print x_wrapped[5]
-5
In [15]: print x_wrapped[-1]
None
In [16]: x_wrapped = NoWrap(x, 'FOO')
In [17]: print x_wrapped[-1]
FOO
While you could subclass e.g. list as suggested by jdi, Python's slicing behaviour is not something anyone's going to expect you to muck about with.
Changing it is likely to lead to some serious head-scratching by other people working with your code when it doesn't behave as expected - and it could take a while before they go looking at the special methods of your subclass to see what's actually going on.
See: Action at a distance
I think this isn't ugly enough to justify new classes and wrapping things.
Then again it's your code.
def foo(i, j, r=2):
'''sum of neighbours within r steps of A[i,j]'''
return A[i-r:abs(i+r+1), j-r:abs(j+r+1)].sum() # ugly, but works?
(Downvoting is fun, so I've added some more options)
I found out something quite unexpected (for me): The __getslice__(i,j) does not wrap! Instead, negative indices are just ignored, so:
lst[1:3] == lst.__getslice__(1,3)
lst[-3:-1] == 2 next to last items but lst.__getslice__(-3,-1) == []
and finally:
lst[-2:1] == [], but lst.__getslice__(-2,1) == lst[0:1]
Surprising, interesting, and completely useless.
If this only needs to apply in a few specific operations, a simple & straightworward if index>=0: do_something(array[i]) / if index<0: raise IndexError would do.
If this needs to apply wider, it's still the same logic, just being wrapped in this manner or another.

Categories