Bracket assignment in Python - python

I would like to create a class that is able to do the following
class Test:
# code here
test = Test()
test["test"] = 1
test.test # returns 1
Is this possible in Python using magic methods (e.g. not inheriting from dict)?

You could override __getitem__ and __setitem__ using getattr and setattr:
class Test:
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
test = Test()
test["test1"] = 1
print(test.test1)
Using this method, you can also set from the attribute and get from the square bracket operator:
test = Test()
test.test2 = 2
print(test["test2"])
If you wanted to use this among many classes then you could turn this into a base class:
class ItemAttributes:
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
class Test(ItemAttributes):
pass

Related

Hijacking the getattr and setattr functions after __init__ completes

I need to intercept setattr and getattr after init completion, i.e. if main class doesn't have required attribute, it would look for it in subclass Extra, or when setting attribute, if it's not in main class then setting went to subclass Extra, how to understand that init was executed and intercept it only after completion? Here's the code I tried to do it with but it didn't work
class Test:
def __init__(self):
self.default_name = "Michael"
def __setattr__(self, key, value):
if not hasattr(self, key):
self.Extra.__dict__[key] = value;
self.__dict__[key] = v
def __getattr__(self, item):
if not hasattr(self, item):
return self.Extra.__dict__[item]
class Extra:
pass
user = Test()
user.default_name = "Tomas"
user.some_data = "test"
print(user.default_name)
print(user.some_data)
Direct operation attribute dictionary:
class Test:
def __init__(self):
vars(self)['default_name'] = "Michael"
vars(self)['extra'] = Test.Extra()
def __setattr__(self, key, value):
if key not in vars(self):
setattr(self.extra, key, value)
else:
vars(self)[key] = value
def __getattr__(self, item):
return getattr(self.extra, item)
class Extra:
pass
Test:
>>> user = Test()
>>> user.default_name
'Michael'
>>> user.default_name = 'Tomas'
>>> user.default_name
'Tomas'
>>> user.some_data = 'test'
>>> user.some_data
'test'
>>> vars(user)
{'default_name': 'Tomas', 'extra': <__main__.Test.Extra object at 0x000001D5151D6380>}

Python class is subscriptable but not iterable

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.

Clean way to implement setter and getter for lots of properties?

I have known the use of setter and getter for several properties, how could I trigger a same function when any property changes?
For example, the following codes add a setter to property a.
class AAA(object):
def __init__(self):
...
#property
def a(self):
...
#a.setter
def a(self, value):
...
If the class has a lot of properties like a, b, ... , z, and I want to print something like property xxx is modified when any property changes.
It is stupid to add the similar getter and setter one by one.
I have read some related questions and answers, but I do not find the solution for many properties.
How to trigger function on value change?
Using #property versus getters and setters
Metaprogramming, using __setattr__ to intercept modification:
class AAA(object):
def __setattr__(self, attr, value):
print("set %s to %s" % (attr, value))
super().__setattr__(attr, value)
aaa = AAA()
aaa.x = 17
# => set x to 17
print(aaa.x)
# => 17
You can do similarly with __getattr__ for reading access.
You can use descriptors. Descriptors are, in layman's terms, reusable properties. The advantage over the __getattr__ and __setattr__ hooks is that you have more fine-grained control over what attributes are managed by descriptors.
class MyDescriptor:
def __init__(self, default='default'):
self.default = default
def __set_name__(self, owner, name): # new in Python3.6
self.name = name
def __get__(self, instance, owner):
print('getting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
if instance is not None:
try:
return vars(instance)[self.name]
except KeyError:
return self.default
return self
def __set__(self, instance, value):
print('setting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
vars(instance)[self.name] = value
class MyClass:
a = MyDescriptor()
b = MyDescriptor()
_id = 1
# some logic for demo __repr__
def __init__(self):
self.c = 'non-descriptor-handled'
self.id = MyClass._id
MyClass._id += 1
def __repr__(self):
return 'MyClass #{}'.format(self.id)
Demo:
>>> m1 = MyClass()
>>> m2 = MyClass()
>>> m1.c
'non-descriptor-handled'
>>> m1.a
getting a on MyClass #1
'default'
>>> m1.b
getting b on MyClass #1
'default'
>>> m1.b = 15
setting b on MyClass #1
>>> m1.b
getting b on MyClass #1
15
>>> m2.b
getting b on MyClass #2
'default'
One year after asking this question, I find a more elgant way to add getter and setter to multiple similar properties.
Just make a more 'abstract' function which returns decorated property. And pass each of these properties to this function with a for loop. Then the getter and setter of all these properties are added.
def propABC(arg):
# arg: 'a', 'b', 'c'
#property
def prop(self):
_arg = '_' + arg
return getattr(self, _arg)
#prop.setter
def prop(self, val):
_arg = '_' + arg
setattr(self, _arg, val)
print(f"Set prop {_arg}")
return prop
for key in ['a', 'b', 'c']:
exec(f"{key} = propABC('{key}')")

Implement _del_ method for a class with __getattribute__ overriden

Taking this question as a pointer, let's say there exists a class like the following:
class Container(object):
def __init__(self, **kwargs):
self._meta = defaultdict(lambda: None)
for attr, value in kwargs.iteritems():
self._meta[attr] = value
def __getattr__(self, key):
try:
return self._meta[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
if key in ('_meta', '_hasattr'):
super(Container, self).__setattr__(key, value)
else:
self._meta[key] = value
This allows the following behavior:
c = Container()
c.a = 1
print(c.a) # 1
print(c.b) # None
Question: What is the best way to implement an operator such that the following works:
# Should delete the value of a from Container._meta
del c.a
Of course, one could obviously implement a method like,
def _delete(self, key):
...
But is there way to re-use a python operator to do this?
Just define the __delattr__ method:
def __delattr__(self, key):
del self._meta[key]

Why overidding is required for a dict class in Python

I was reading source code of a banking application and the class bank is defined below:
class Bank(object):
""" the bank class contains all the bank operations """
def __init__(self, name):
""" instantiate the class """
self.name = str(name)
self.customers = Customers()
Now self.customers is another instance of Customers class which is defined below:
class Customers(dict):
""" the customers class extends the dictionary object """
def __setitem__(self, key, item):
self.__dict__[key] = item
def __getitem__(self, key):
return self.__dict__[key]
def __repr__(self):
return repr(self.__dict__)
def __len__(self):
return len(self.__dict__)
def __delitem__(self, key):
del self.__dict__[key]
def keys(self):
return self.__dict__.keys()
def values(self):
return self.__dict__.values()
def __cmp__(self, dict):
return cmp(self.__dict__, dict)
def __contains__(self, item):
return item in self.__dict__
def add(self, key, value):
self.__dict__[key] = value
def __iter__(self):
return iter(self.__dict__)
def __call__(self):
return self.__dict__
def __unicode__(self):
return unicode(repr(self.__dict__))
As per my understanding we override a function when a new functionality is added or its behaviour is changed from previous function. Why we are overriding the functions of dict in Customer class. Can't we simply just use self.customers = dict() ? Since we are not adding anything new here.
The class is more than just a dict; it supports attribute access for the keys too, because it delegates all dictionary access to the instance __dict__ attribute, which contains all attributes.
Demo:
>>> c = Customers()
>>> c.foo = 'bar'
>>> c
{'foo': 'bar'}
>>> c['foo']
'bar'
You can't do that with a regular dictionary. The implementation you found is a rather elaborate version of the answers to this question: Accessing dict keys like an attribute in Python?

Categories