Python, why is the dataclass attribute read-only? - python

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).

Related

printing variable inside a def, inside a class

I am new to object oriented programming, what I want to do basically is print a variable inside a def which is in its turn inside a class, I think there's probably a very simple answer but I just can't figure it out, thanks for the assistance, here's my code:
class test():
def test2():
x = 12
print(test.test2.x)
this gives me the following error:
Traceback (most recent call last):
File "/home/vandeventer/x.py", line 4, in <module>
print(test.test2.x)
AttributeError: 'function' object has no attribute 'x'
when I try:
class test():
def test2():
x = 12
print(test.x)
I get:
Traceback (most recent call last):
File "/home/vandeventer/x.py", line 4, in <module>
print(test.x)
AttributeError: type object 'test' has no attribute 'x'
You can't do what you want; local variables only exist during the lifetime of a function call. They are not attributes of the function nor are they available outside of the call in any other way. They are created when you call the function, destroyed again when the function exits.
You can set attributes on function objects, but those are independent of locals:
>>> class test():
... def test2():
... pass
... test2.x = 12
...
>>> test.test2.x
12
If you need to keep a value a function produced, either return the value, or assign it to something that lasts longer than the function. Attributes on the instance are a common place to keep things:
>>> class Foo():
... def bar(self):
... self.x = 12
...
>>> f = Foo()
>>> f.bar()
>>> f.x
12
If you want to print that value you could also use a return statement and the self parameter.
class test():
def test2(self):
x = 12
return x
test = test()
print(test.test2())
I do not know if this fully answers your questions but it is a way to print your x.

Adding a new attribute to Python object?

Being from OOPS background, It looks strange to see below code from link
def f():
f.beencalled = True
return 0
My question:
1)
From the above code,
Is f a reference variable pointing to an object f of class 'function'?
2)
We add a new attribute beencalled to an object f, so now 'function' class does not have this attribute beencalled defined and we say that object f is an object of class 'function'? Does it make sense?
1) Yes:
>>> def f():
print(type(f))
>>> f()
>>> <class 'function'>
2) The function class does not have a new attribute, but the object f does. Adding or removing attributes to/from an object does not affect which attributes other objects of that class will have:
>>> class A: pass
>>> a = A()
>>> a.var = 7
>>> b = A()
>>> b.var
Traceback (most recent call last):
File "<pyshell#19>", line 1, in <module>
b.newvar
AttributeError: 'A' object has no attribute 'var'
Classes are much more flexible in python than in Java or C++. Objects can have attributes not defined in their class, or even lack attributes that were defined in their class! Look at this:
>>> class A:
def __init__(self, a):
self.var = a
>>> obj = A(7)
>>> del obj.var #deletes the var attribute from obj, does not change the A class
>>> obj.var
Traceback (most recent call last):
File "<pyshell#28>", line 1, in <module>
obj.var
AttributeError: 'A' object has no attribute 'var'
>>> obj2 = A(6)
>>> obj2.var #obj2 is a new object, so the fact we deleted var from obj doesn't affect it
6
EDIT: after a bit of searching I found an explanation for why this behavior was chosen (source):
To implement user-defined objects, I settled on the simplest possible
design; a scheme where objects were represented by a new kind of
built-in object that stored a class reference pointing to a "class
object" shared by all instances of the same class, and a dictionary,
dubbed the "instance dictionary", that contained the instance
variables.
In this implementation, the instance dictionary would contain the
instance variables of each individual object whereas the class object
would contain stuff shared between all instances of the same class--in
particular, methods. In implementing class objects, I again chose the
simplest possible design; the set of methods of a class were stored in
a dictionary whose keys are the method names. This, I dubbed the class
dictionary. To support inheritance, class objects would additionally
store a reference to the class objects corresponding to the base
classes. At the time, I was fairly naïve about classes, but I knew
about multiple inheritance, which had recently been added to C++. I
decided that as long as I was going to support inheritance, I might as
well support a simple-minded version of multiple inheritance. Thus,
every class object could have one or more base classes.
In this implementation, the underlying mechanics of working with
objects are actually very simple. Whenever changes are made to
instance or class variables, those changes are simply reflected in the
underlying dictionary object. For example, setting an instance
variable on an instance updates its local instance dictionary.
Likewise, when looking up the value of a instance variable of an
object, one merely checks its instance dictionary for the existence of
that variable. If the variable is not found there, things become a
little more interesting. In that case, lookups are performed in the
class dictionary and then in the class dictionaries of each of the
base classes.
On a slightly different note, you can change this behavior for custom classes.
class FooBar(object):
__slots__ = ["foo","bar","baz"]
# if you don't define __slots__, you can add attr to the object as needed
# if you do, the object can only contain those attributes.
def __init__(self,foo=None,bar=None,baz=None):
self.foo = foo
self.bar = bar
self.baz = baz
def __str__(self):
return "I'm a FooBar with id {0} with foo: {1.foo}, bar: {1.bar}, baz: {1.baz}".format(id(self),self)
>>> a = FooBar("a","B","CCC")
>>> print(a)
I'm a FooBar with id 47260256 with foo: a, bar: B, baz: CCC
>>> a.spam = "eggs"
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
a.spam = "eggs"
AttributeError: 'FooBar' object has no attribute 'spam'
Alternately, without defining __slots__:
class BooFar(object):
def __str__(self):
return "I'm a BooFar with the following attributes:\n{}".format(self.__dict__)
>>> b = BooFar()
>>> print(b)
I'm a BooFar with the following attributes:
{}
>>> b.spam = "eggs"
>>> print(b)
I'm a BooFar with the following attributes:
{'spam': 'eggs'}
f() in just an instance of types.FunctionType, and instances can have their own attributes.
Adding an attribute to an instance won't affect its class unless you've overridden the __setattr__ method of that class and doing something evil there.
>>> import types
>>> def func(): pass
>>> isinstance(func, types.FunctionType)
True

Questions regarding Python and Class specific variables

I have a question regarding python and class initialized variables.
So I recently noticed in Python (2.7.X) that if you set a class variable that hasn't been defined or initialized, you are still able to call and access the data within that variable.
For instance:
class Test:
def __init__(self):
self.a = "Hello"
t = Test()
print t.a
t.b = "World"
print t.b
Output:
Hello
World
I would expect 'print t.b' to error because b hasn't been defined in the Test() class but it runs without any issue. Why is this happening? Can anyone explain?
http://ideone.com/F2LxLh
Thank you for your time.
From the docs on instance objects (t is an instance object because it is an instance of the custom class Test):
Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to.
However you can get your expected behavior by using __slots__ with a new-style class. This overrides the default dictionary storage for attributes to make the object more memory efficient, and it also results in an AttributeError if you try to assign to an attribute not defined in __slots__, for example:
>>> class Test(object):
... __slots__ = ['a']
...
>>> t = Test()
>>> t.a = "Hello"
>>> t.b = "World"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'b'
This is expected behaviour. You can add attributes in Python at any time without errors. Even without setting attributes in the __init__ you can add new ones on the fly:
>>> class Test:
... pass
...
>>> t = Test()
>>> t.foo = '3'
>>> t.foo
'3'
If you want you can change this behavior by writing your own __setattr__ method ( see docs )
class Test:
def __init__(self):
self.__dict__[ 'a' ] = "Hello"
def __setattr__( self, name, value ):
if name not in self.__dict__:
raise Exception( 'No attribute: ' + name )
else:
self.__dict__[ name ] = value
t = Test()
t.a = 'hello world'
print ( t.a )
t.b = "World" # <<< this will throw exception

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

Categories