How to assign a new class attribute via __dict__? - python

I want to assign a class attribute via a string object - but how?
Example:
class test(object):
pass
a = test()
test.value = 5
a.value
# -> 5
test.__dict__['value']
# -> 5
# BUT:
attr_name = 'next_value'
test.__dict__[attr_name] = 10
# -> 'dictproxy' object does not support item assignment

There is a builtin function for this:
setattr(test, attr_name, 10)
Reference: http://docs.python.org/library/functions.html#setattr
Example:
>>> class a(object): pass
>>> a.__dict__['wut'] = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'dictproxy' object does not support item assignment
>>> setattr(a, 'wut', 7)
>>> a.wut
7

Related

Python, why is the dataclass attribute read-only?

I have a dataclass which its signature gets updated based on the active configs before it gets initiated and may be modified after initiation. Accordingly, I have something like this:
from dataclasses import dataclass, field
#dataclass(slots=True, frozen=False)
class MyClass:
att_1: str = field(init=False)
att_2: str = field(init=False)
#classmethod
def update_class(cls, first, second):
cls.att_1 = first
cls.att_2 = second
My problem is that although the dataclass is not frozen, when I initiate the class I cannot change the attributes anymore. This is a sample of what I do.
MyClass.update_class("11", "12")
print(MyClass.att_1)
a = MyClass()
print(a)
print(MyClass.att_2)
print(MyClass.att_1)
a.att_2 = "002"
print(a)
Running this I get what is shown bellow:
11
MyClass(att_1='11', att_2='12')
12
11
Traceback (most recent call last):
File "/b.py", line 25, in <module>
a.att_2 = "002"
AttributeError: 'MyClass' object attribute 'att_2' is read-only
Can someone tell me why is att_2 read-only and how can I fix this?
So, the problem is that you are using __slots__ and then removing the __slots__ descriptors, breaking your class. So, consider (without the dataclass involved):
>>> class Foo:
... __slots__ = "x", "y"
...
>>> foo = Foo()
>>> foo.x = 1
>>> foo.x
1
>>> Foo.x
<member 'x' of 'Foo' objects>
>>> Foo.x = None
>>> foo.x
>>> foo.x = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object attribute 'x' is read-only
So, one simple solution is to not use __slots__, but it really isn't clear to me how this is all supposed to work or why you have an update_class method (and what it's supposed to accomplish).

Enum KeyError Function

I am trying to access an function stored inside an Enum using it's name but I get a KeyError:
from enum import Enum
def f():
pass
class MyEnum(Enum):
function = f
print MyEnum.function # <unbound method MyEnum.f>
print MyEnum['function'] # KeyError: 'function'
But it work if the Enum don't store func:
from enum import Enum
class MyEnum(Enum):
a = "toto"
print MyEnum.a # MyEnum.a
print MyEnum.a.value # "toto"
print MyEnum['a'] # MyEnum.a
print MyEnum.a.value # "toto"
I know I could use dict instead of Enum, but I want to know why Enum behave differently.
Assigning a function is the same as defining it. And if you define a function in an Enum it becomes a method of the Enum and is not taken as a value for enum.
The following enums A and B are completely equivalent:
>>> from enum import Enum
>>>
>>> class A(Enum):
... a = 1
... def f(self):
... print('Hello')
...
>>> def x(self):
... print('Hello')
...
>>> class B(Enum):
... a = 1
... f = x
...
>>> B.f
<unbound method B.x>
>>> A.f
<unbound method A.f>
>>> A['a']
<A.a: 1>
>>> A['f']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dist-packages/enum/__init__.py", line 384, in __getitem__
return cls._member_map_[name]
KeyError: 'f'
>>> B['a']
<B.a: 1>
>>> B['f']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dist-packages/enum/__init__.py", line 384, in __getitem__
return cls._member_map_[name]
KeyError: 'f'
Functions are treated differently because otherwise it would impossible to define custom methods in an enum.

Why do Python's empty classes and functions work as arbitrary data containers, but not other objects?

I've seen two different Python objects used to group arbitrary data together: empty classes and functions.
def struct():
pass
record = struct
record.number = 3
record.name = "Zoe"
class Struct:
pass
record = Struct()
record.number = 3
record.name = "Zoe"
Even if the class isn't empty, it seems to work so long as it's defined at runtime.
But when I got cocky and tried to do this with built-in functions or classes, it didn't work.
record = set()
record.number = 3
AttributeError: 'set' object has no attribute 'number'
record = pow
pow.number = 3
AttributeError: 'builtin_function_or_method' object has no attribute 'number'
Is there a fundamental difference between built-in and "custom" classes and functions that accounts for this behavior?
The difference is that both function objects and your Struct object have a __dict__ attribute, but set instances and built-in functions do not:
>>> def struct():
... pass
...
>>> record = struct
>>> record.number = 2
>>> struct.__dict__
{'number': 2}
>>> class Struct:
... pass
...
>>> record = Struct()
>>> record.number = 3
>>> record.__dict__
{'number': 3}
>>> record=set()
>>> record.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'set' object has no attribute '__dict__'
>>> pow.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'builtin_function_or_method' object has no attribute '__dict__'
In classes you can emulate the behavour using slots (although only on new-style classes):
>>> class StructWithSlots(object):
... __slots__ = []
...
>>> record = StructWithSlots()
>>> record.number = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'StructWithSlots' object has no attribute 'number'
>>> record.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'StructWithSlots' object has no attribute '__dict__'
Built-in types are written in C and cannot be modified like that. But after the type/class unification introduced in py2.2 you can now inherit from a built-in types and override or add your own attributes to that subclass.
You can use the forbiddenfood package to add attributes to built-in types:
This project aims to give you the way to find heaven in tests, but it
might lead you to hell if you use it on production code.
>>> from forbiddenfruit import curse
>>> def words_of_wisdom(self):
... return self * "blah "
>>> curse(int, "words_of_wisdom", words_of_wisdom)
>>> assert (2).words_of_wisdom() == "blah blah "
And of course if you're cocky enough then you can create your own types in C and add such features to it.
Some built-ins can be more restrictive. Also, classes implemented with slots won't accept arbitrary attributes either.
If you want some simular protection in your own class, you can use the __setattr__() method.
class TestClass(object):
# Accept the attributes in this list
__valid_attributes = ["myattr1", "myattr2"]
def __setattr__(self, name, value):
if not name in TestClass.__valid_attributes:
raise AttributeError(
"{0} has no attribute '{1}'".format(self.__class__.__name__, name))
self.__dict__[name] = value
Now you can do something like this:
t = TestClass()
t.noattr = "test" # AttributeError: TestClass has no attribute 'noattr'
But "valid attributes" can still be set:
t = TestClass()
t.myattr1 = "test"
print(t.myattr1) # test

Impossible to set an attribute to a string?

Usually, you can set an arbitrary attribute to a custom object, for instance
----------
>>> a=A()
>>> a.foo=42
>>> a.__dict__
{'foo': 42}
>>>
----------
On the other hand, you can't do the same binding with a string object :
----------
>>> a=str("bar")
>>> a.foo=42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'foo'
>>> a.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__dict__'
>>>
----------
Why ?
Because the str type is a type wich does not has an attribute dict. From the docs, "Classes" section:
A class has a namespace implemented by a dictionary object.
Class attribute references are translated to lookups in this
dictionary, e.g., C.x is translated to C.__dict__["x"]
You can also enforce something similar on custom objects:
>>> class X(object):
... __slots__=('a', )
...
>>> a = X()
>>> a.a = 2
>>> a.foo = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'X' object has no attribute 'foo'
In general, you should not be setting nor modifying fields of objects that you are not supposed to. The documentation of the specific data type should reference you what fields are available for public modification.
For example, an ReadOnlyPoint object, where the x and y coordinates are set only on object construction:
>>> class ReadOnlyPoint(object):
... __slots__ = ('_x', '_y')
... def __init__(self, x, y):
... self._x = x
... self._y = y
... def getx(self):
... return self._x
... def gety(self):
... return self._y
... x = property(getx)
... y = property(gety)
...
>>> p = ReadOnlyPoint(2, 3)
>>> print p.x, p.y
2 3
>>> p.x = 9
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> p._x = 9
>>> print p.x, p.y
9 3
While the x and y properties are read-only, accessing the object internals allows you to alter the object's state.
The inhability to add a new field to an str object is an implementation detail, specific to the Python version that you are using.
http://docs.python.org/reference/datamodel.html
If the class has a setattr() or delattr() method, this is
called instead of updating the instance dictionary directly.
http://docs.python.org/reference/datamodel.html#object.setattr

How to "uninstantiate" an object?

I wrote a function in Python:
def instantiate(c):
if inspect.isclass(c): return c()
elif isinstance(c, object): return c
else: raise Exception, '%s is not an object or class.' % c
Now I want to do the opposite: get the class from an already instantiated object so that I can re-instantiate it with different parameters. How can I do that?
Tests:
>>> f = Form()
>>> type(f)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: instance() takes at least 1 argument (0 given)
>>> f.__class__()
<forms.Form instance at 0xb7f4d5cc>
More tests:
>>> o = object()
>>> type(o)()
<object object at 0xb7f78478>
>>> o.__class__()
<object object at 0xb7f78480>
Seems to work for object but not my Form class:
class Form:
def __init__(self, data={}, prefix='', action='', id=None):
I'm guessing this has something to do with self but I don't know what.
To get the class of x
x.__class__
The class of object c is type(c).

Categories