Python class is subscriptable but not iterable - python

I am trying to expose the classes dictionary making it both and subscriptable and be able to iterate through the dict values. Here is the class :
class ExampleClass():
def __init__(self, *args, **kwargs):
for key, value in self.kwargs.items():
setattr(self, key, value)
for arg in args:
setattr(self, arg, arg) if isinstance(arg, str) else setattr(self, str(arg), arg)
def __str__(self):
return 'This is the example class'
def __getitem__(self, obj):
return self.__dict__[obj]
def __len__(self):
return len(self.__dict__.items())
If we create an instance and pass in these values :
cls = ExampleClass(123456,'cash', name='newexample', id=1)
This will store all of the args and kwargs as instance attributes, and using the syntax cls['id'] will return 1 as expected. But when I use the syntax for i in cls: print(i) I get a KeyError : KeyError : 0
How can I make this object's dict both subscriptable and iterable ?

You need to implement the __iter__ method.
class ExampleClass():
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
for arg in args:
setattr(self, arg, arg) if isinstance(arg, str) else setattr(self, str(arg), arg)
def __str__(self):
return 'This is the example class'
def __getitem__(self, obj):
return self.__dict__[obj]
def __len__(self):
return len(self.__dict__.items())
def __iter__(self):
return iter(self.__dict__)
cls = ExampleClass(123456,'cash', name='newexample', id=1)
print(cls['cash'])
print(cls['name'])
for i in cls: print(i)
This is the method which is called to create an iterator for your type so that it can be iterated. Your underlying dict already implements it, so you're sort of just proxying it here.

To make a class subscriptable, it must contain dunder getitem(), it may or may not contain dunder iter().
At the same time, an iterable must contain iter().
To check if your class has the required method, perform print(dir(your_class)), and look for the respective dunder function.
If you don't have one, create it.

Related

Attributes as dictionary values

I am attempting to set attributes on a dict key and have a feeling my approach is wrong. How would I get the current key inside alfa? Alfa in production will be used to create and update a dataclass.
class Dictn(dict):
def __init__(self, *args, **kwds):
super(Dictn, self).__init__(*args, **kwds)
self.__dict__ = self
def __getitem__(self, key):
if key not in self.keys():
self.__setitem__(key, None)
dict.__getitem__(self, key)
return self
def alfa(self, **kwds):
''' Need current key to set attributes on it '''
self.__setattr__(**kwds)
def beta(self, **kwds):
''' Need current key to set attributes on it '''
self.__setattr__(**kwds)
dictn = Dictn()
dictn['ABC'].alfa(es=1, te=2)
dictn['ABC'].beta(es=3, te=4)
A dictionary stores objects that can be recovered as key-value pairs. (Remember, everything in python is an object.) If you want those objects to have additional attributes, you need to edit them directly, not the dict class which only acts as a container. The dict doesn't really care what it stores, and it certainly isn't (and shouldn't be) responsible for handling any properties on its contents.
(By the way, dictn["ABC"] represents a value in the dictionary, not a key. They key here is "ABC". I'm not sure if you're just confusing the definitions here or if you mean something else entirely.)
You could set an instance variable to the key used in __getitem__().
class Dictn(dict):
def __init__(self, *args, **kwds):
super(Dictn, self).__init__(*args, **kwds)
self.__dict__ = self
def __getitem__(self, key):
if key not in self.keys():
self.__setitem__(key, None)
self.current_key = key
dict.__getitem__(self, key)
return self
def alfa(self, **kwds):
// use self.current_key to get current_key to set attributes on it
self.__setattr__(**kwds)
def beta(self, **kwds):
// use self.current_key to get current_key to set attributes on it
self.__setattr__(**kwds)
Solution is to use a class as an attribute setter:
#dataclass()
class Position:
es: int = None
te: int = None
class ValueObj():
def alfa(self, **kwds):
setattr(self, 'alfa', Position(**kwds))
def beta(self, **kwds):
setattr(self, 'beta', Position(**kwds))
class Dictn(dict):
def __init__(self, *args, **kwds):
super(Dictn, self).__init__(*args, **kwds)
self.__dict__ = self
def __getitem__(self, key):
if key not in self.keys():
self.__setitem__( key,
ValueObj(),
)
return dict.__getitem__(self, key)
dictn = Dictn()
dictn['ABC'].alfa(es=1, te=2)
dictn['ABC'].alfa
>> Position(es=1, te=2)
This answer was posted as an edit to the question Attributes as dictionary values by the OP misantroop under CC BY-SA 4.0.

Python: how to disable creation of new keys in attribute dict?

Here is a simple code of attribute dict:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
More context:
https://stackoverflow.com/a/14620633/1179925
I can use it like:
d = AttrDict({'a':1, 'b':2})
print(d)
I want this to be possible:
d.b = 10
print(d)
But I want this to be impossible:
d.c = 4
print(d)
Is it possible to throw an error on new key creation?
You could check if they are already in there
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
def __setattr__(self, key, value):
if key not in [*self.keys(), '__dict__']:
raise KeyError('No new keys allowed')
else:
super().__setattr__(key, value)
def __setitem__(self, key, value):
if key not in self:
raise KeyError('No new keys allowed')
else:
super().__setitem__(key, value)
First I thought this would be a bad idea since no initial values could be added but from the builtins it states the following:
dict(iterable) -> new dictionary initialized as if via:
d = {}
for k, v in iterable:
d[k] = v
So this does allow you to change the methods without them having effect on the initialization of the Class as it creates a new one from {} instead of from its own instance.
They will be able to change __ dict __ always though..
You can override the __setattr__ special method.
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__setattr__('_initializing', True)
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
super(AttrDict, self).__setattr__('_initializing', False)
def __setattr__(self, x, value):
if x == '_initializing':
raise KeyError("You should not edit _initalizing")
if self._initializing or x in self.__dict__.keys():
super(AttrDict, self).__setattr__(x, value)
else:
raise KeyError("No new keys allowed!")
Note that I needed to add an attribute _initializing to let __setattr__ distinguish between attributes created by __init__ and attributes created by users.
Since python does not have private attributes, users might still set _initializing to True and then add their attributes to the AttrDict instance, so I added a further check to be sure that they are not trying to edit _initializing.
It is still not 100% safe, since an user could still use super() to set _initializing to True.

How can I instantiate class only if argument passed is string?

I created a class 'Stage' and want instantiate it only if argument passed to init(arg)
#example code
class Stage:
def __init__(self, arg):
if type(arg) == str:
#create object
else:
#do not create object
#main:
# entry = input()
obj = Stage(entry)
if obj:
print("created") # if entry is string
else:
print("not created") # if entry is float
Raise an exception:
def __init__(self, arg):
if not isinstance(arg, str):
raise TypeError("Stage.__init__ called with a non-str value: %r" % (arg,))
# continue initializing the object
However, consider whether it the value really needs to be a str, or just something that can be turned into a str:
def __init__(self, arg):
arg = str(arg)
# ...
If you want to avoid creating the instance altogether, you need to override __new__, not __init__ (with some of the previous advice folded in):
class Stage:
def __new__(cls, arg):
try:
arg = str(arg)
except ValueError:
raise TypeError("Could not convert arg to str: %r" % (arg, ))
return super().__new__(cls, arg)
Check for the type of argument before instantiating your object. Also consider using isinstance to check for a type, instead of type
class Stage:
def __init__(self, arg):
pass
if isinstance(str, entry):
obj = Stage(entry)
else:
raise TypeError('A str-type is required as an argument to the constructor')
You cannot initialize an object with that condition, but you can throw an error
class Stage:
def __init__(self, arg):
if not isinstance(arg, str):
raise TypeError("non-str value: %r was passed, str type argument required " % (arg,))
You can also use a classmethod to create an instance only if the passed value is a string:
class Stage:
def __init__(self, val):
self.val = val
#classmethod
def stage(cls, arg):
return None if not isinstance(arg, str) else cls(arg)
s = Stage.stage("name")
Now, s will either be instance of Stage if arg is a string or None if arg is any other type.

Polymorphic class in Python that returns an instance of another class

I have class container that transmute itself into another class.
For example I have some types such as MyFloat MyStr or MyInt that offer additional methods or attributes. I would like to encapsulate the decision to build any of these types into another class:
My first attempt was to write this:
class MyFloat(float):
def foo_float():
pass
class MyStr(str):
def foo_str():
pass
class MyInt(int):
def foo_int():
pass
# Does not work
class Polymorph(object):
def __init__(self, value):
if isinstance(value, float):
self = MyFloat(value)
elif isinstance(value, int):
self = MyInt(value)
elif isinstance(value, str):
self = MyStr(value)
else:
raise TypeError, 'Unknown type'
Unfortunately I did not get the expected instance at the end:
>>> a = Polymorph(42.42) # Should get an instance of MyFloat
>>> type(a)
__main.MyFloat
I then tried to use __new__ instead
class Polymorph(object):
def __new__(cls, value):
if isinstance(value, float):
return super(MyFloat, cls).__new__(cls, value)
elif isinstance(value, int):
return super(MyInt, cls).__new__(cls, value)
elif isinstance(value, str):
return super(MyStr, cls).__new__(cls, value)
else:
raise TypeError, 'Unknown type'
But this time I get a TypeError: super(type, obj): obj must be an instance or subtype of type
Is it possible to achieve this?
So I found this solution that works. However, I don't know is it is Pythonic Acceptable to do this.
class Polymorph(object):
def __new__(cls, value):
if isinstance(value, float):
return MyFloat(value)
elif isinstance(value, int):
return MyInt(value)
elif isinstance(value, str):
return MyStr(value)
else:
raise TypeError, 'Unknown type'

Python dictionary with memory of keys that were accessed?

I would like to create a data structure that behaves like a dictionary with one added functionality which is to keep track of which keys have been "consumed". Please note that I can't just pop the values as they are being reused.
The structure should support these three cases, i.e. mark the key as consumed when accessed as:
if key in d:
...
d[key]
d.get(key)
This is what I have written:
class DictWithMemory(dict):
def __init__(self, *args, **kwargs):
self.memory = set()
return super(DictWithMemory, self).__init__(*args, **kwargs)
def __getitem__(self, key):
self.memory.add(key)
return super(DictWithMemory, self).__getitem__(key)
def __contains__(self, key):
self.memory.add(key)
return super(DictWithMemory, self).__contains__(key)
def get(self, key, d=None):
self.memory.add(key)
return super(DictWithMemory, self).get(key, d)
def unused_keys(self):
"""
Returns the list of unused keys.
"""
return set(self.keys()).difference(self.memory)
As I am not very familiar with the internals of dict, is there a better way to achieve this result?
Here's a solution that abstracts everything away inside a metaclass. I'm not sure if this is really any more elegant, but it does provide some amount of encapsulation should you change your mind about how to store the used keys:
class KeyRememberer(type):
def __new__(meta, classname, bases, classDict):
cls = type.__new__(meta, classname, bases, classDict)
# Define init that creates the set of remembered keys
def __init__(self, *args, **kwargs):
self.memory = set()
return super(cls, self).__init__(*args, **kwargs)
cls.__init__ = __init__
# Decorator that stores a requested key in the cache
def remember(f):
def _(self, key, *args, **kwargs):
self.memory.add(key)
return f(self, key, *args, **kwargs)
return _
# Apply the decorator to each of the default implementations
for method_name in [ '__getitem__', '__contains__', 'get' ]:
m = getattr(cls, method_name)
setattr(cls, method_name, remember(m))
return cls
class DictWithMemory(dict):
# A metaclass that ensures the object
# has a set called 'memory' as an attribute,
# which is updated on each call to __getitem__,
# __contains__, or get.
__metaclass__ = KeyRememberer
def unused_keys(self):
"""
Returns the list of unused keys.
"""
print "Used", self.memory
return list(set(super(DictWithMemory,
self).keys()).difference(self.memory))

Categories