This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why can't I directly add attributes to any python object?
Why can't you add attributes to object in python?
The following code does not throw AttributeError
class MyClass():
def __init__(self):
self.a = 'A'
self.b = 'B'
my_obj = MyClass()
my_obj.c = 'C'
That contrasts with
>>> {}.a = 'A'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'a'
What makes such difference? Is it about dict being a built-in class while MyClass being user defined?
The difference is that instances of user-defined classes have a dictionary of attributes associated with them by default. You can access this dictionary using vars(my_obj) or my_obj.__dict__. You can prevent the creation of an attribute dictionary by defining __slots__:
class MyClass(object):
__slots__ = []
Built-in types could also provide an attribute dictionary, but usually they don't. An example of a built-in type that does support attributes is the type of a function.
Related
This question already has answers here:
Validating detailed types in python dataclasses
(4 answers)
Force type conversion in python dataclass __init__ method
(8 answers)
Closed 3 years ago.
In this code:
import dataclasses
#dataclasses.dataclass
class MyClass:
value: str
obj = MyClass(value=1)
the dataclass MyClass is instantiated with a value that does not obey the value type.
Is there a simple way (using a decorator, an argument in the dataclass decorator or library) of enforcing the fields' types so that the last line in my example raises a ValueError or something like that? Is there a major downside of enforcing types in this way?
You can declare a custom __post_init__ method (see python's doc) and put all checks there to force type checking. This method can be declare in parent's class to reduce changes amount.
import dataclasses
#dataclasses.dataclass()
class Parent:
def __post_init__(self):
for (name, field_type) in self.__annotations__.items():
if not isinstance(self.__dict__[name], field_type):
current_type = type(self.__dict__[name])
raise TypeError(f"The field `{name}` was assigned by `{current_type}` instead of `{field_type}`")
print("Check is passed successfully")
#dataclasses.dataclass()
class MyClass(Parent):
value: str
obj1 = MyClass(value="1")
obj2 = MyClass(value=1)
The results:
Check is passed successfully
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 3, in __init__
File "<stdin>", line 7, in __post_init__
TypeError: The field `value` was assigned by `<class 'int'>` instead of `<class 'str'>`
Let's say this is my class:
class A:
def __init__(self):
self.good_attr = None
self.really_good_attr = None
self.another_good_attr = None
Then a caller can set the values on those variables:
a = A()
a.good_attr = 'a value'
a.really_good_attr = 'better value'
a.another_good_attr = 'a good value'
But they can also add new attributes:
a.goood_value = 'evil'
This is not desirable for my use case. My object is being used to pass a number of values into a set of methods. (So essentially, this object replaces a long list of shared parameters on a few methods to avoid duplication and clearly distinguish what's shared and what's different.) If a caller typos an attribute name, then the attribute would just be ignored, resulting in unexpected and confusing and potentially hard to figure out behavior. It would be better to fail fast, notifying the caller that they used an attribute name that will be ignored. So something similar to the following is the behavior I would like when they use an attribute name that doesn't already exist on the object:
>>> a.goood_value = 'evil'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'goood_value'
How can I achieve this?
I would also like to note that I'm fully aware that a caller can create a new class and do whatever they want, bypassing this entirely. This would be unsupported behavior, though. Making the object I do provide just creates a fail-fast bonehead check to save time against typos for those who do leverage the object I'm providing (myself included), rather than making them scratch their heads wondering why things are behaving in unexpected ways.
You can hook into attribute setting with the __setattr__ method. This method is called for all attribute setting, so take into account it'll be called for your 'correct' attributes too:
class A(object):
good_attr = None
really_good_attr = None
another_good_attr = None
def __setattr__(self, name, value):
if not hasattr(self, name):
raise AttributeError(
'{} instance has no attribute {!r}'.format(
type(self).__name__, name))
super(A, self).__setattr__(name, value)
Because good_attr, etc. are defined on the class the hasattr() call returns True for those attributes, and no exception is raised. You can set those same attributes in __init__ too, but the attributes have to be defined on the class for hasattr() to work.
The alternative would be to create a whitelist you could test against.
Demo:
>>> a = A()
>>> a.good_attr = 'foo'
>>> a.bad_attr = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 10, in __setattr__
AttributeError: A instance has no attribute 'bad_attr'
A determined developer can still add attributes to your instance by adding keys to the a.__dict__ instance dictionary, of course.
Another option is to use a side-effect of using __slots__; slots are used to save memory as a dictionary takes a little more space than just putting values directly into the C structure Python creates for each instance (no keys and dynamic table are needed then). That side-effect is that there is no place for more attributes on such a class instance:
class A(object):
__slots__ = ('good_attr', 'really_good_attr', 'another_good_attr')
def __init__(self):
self.good_attr = None
self.really_good_attr = None
self.another_good_attr = None
The error message then looks like:
>>> a = A()
>>> a.good_attr = 'foo'
>>> a.bad_attr = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'bad_attr'
but do read the caveats listed in the documentation for using __slots__.
Because there is no __dict__ instance attribute when using __slots__, this option really closes the door on setting arbitrary attributes on the instances.
A more idiomatic option is to use a named tuple.
Python 3.6 and higher
In Python 3.6 and higher, you can use typing.NamedTuple to achieve this very easily:
from typing import NamedTuple, Any
class A(NamedTuple):
good_attr: Any = None
really_good_attr: Any = None
another_good_attr: Any = None
More specific type constraints can be used if desired, but the annotations must be included for NamedTuple to pick up on the attributes.
This blocks not only the addition of new attributes, but also the setting of existing attributes:
>>> a = A()
>>> a.goood_value = 'evil'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'goood_value'
>>> a.good_attr = 'a value'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
This forces you to specify all the values at construction time instead:
a = A(
good_attr='a value',
really_good_attr='better value',
another_good_attr='a good value',
)
Doing so is typically not a problem, and when it is, it can be worked around with the judicious use of local variables.
Python 3.5 and lower (including 2.x)
These versions of Python either do not have the typing module or typing.NamedTuple does not work as used above. In these versions, you can use collections.namedtuple to achieve mostly the same effect.
Defining the class is simple:
from collections import namedtuple
A = namedtuple('A', ['good_attr', 'really_good_attr', 'another_good_attr'])
And then construction works as above:
a = A(
good_attr='a value',
really_good_attr='better value',
another_good_attr='a good value',
)
However, this does not allow for the omission of some values from calling the constructor. You can either include None values explicitly when constructing the object:
a = A(
good_attr='a value',
really_good_attr=None,
another_good_attr='a good value',
)
Or you can use one of several techniques to give the argument a default value:
A.__new__.func_defaults = (None,) * 3
a = A(
good_attr='a value',
another_good_attr='a good value',
)
make the parameter private by adding two underscores to it, ex self.__good_attr, this way someone can't set that parameter outside of the class. Then make a function that sets the __good_attr variable and have that function throw an exception if it's wrong.
This question already has answers here:
Why can't you add attributes to object in python? [duplicate]
(2 answers)
Closed 9 years ago.
I was trying out dynamic attribute assignment for testing purposes and discovered following behavior:
>>> class Foo(object): pass
...
>>> bar = Spam()
>>> bar.a = 1
>>> spam = object()
>>> spam.a = 2
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'object' object has no attribute 'a'
Why is the first version with a derived class legit, but the second direct usage of object not? It seems a bit strange to me because deriving hasn't changed anything that has obviously something to do with how variable assignment is handled.
That's because object is a native type, meaning that it's implemented in C code and does not support dynamic attribute assignment, for performance reasons. The same can be said for most Python native classes, such as str or int.
But Python allows you to subclass any native type and your subclasses do support dynamic assignment.
You can disable it for performance reasons on your classes too, using the __slots__ special attribute.
object instances don't have a __dict__.
>>> hasattr(object(), '__dict__')
False
And therefore can't have any attributes added to them.
It's a thing that bugged me for a while. Why can't I do:
>>> a = ""
>>> a.foo = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'foo'
...while I can do the following?
>>> class Bar():
... pass
...
>>> a = Bar()
>>> a.foo = 10 #ok!
What's the rule here? Could you please point me to some description?
You can add attributes to any object that has a __dict__.
x = object() doesn't have it, for example.
Strings and other simple builtin objects also don't have it.
Classes using __slots__ also do not have it.
Classes defined with class have it unless the previous statement applies.
If an object is using __slots__ / doesn't have a __dict__, it's usually to save space. For example, in a str it would be overkill to have a dict - imagine the amount of bloat for a very short string.
If you want to test if a given object has a __dict__, you can use hasattr(obj, '__dict__').
This might also be interesting to read:
Some objects, such as built-in types and their instances (lists, tuples, etc.) do not have a __dict__. Consequently user-defined attributes cannot be set on them.
Another interesting article about Python's data model including __dict__, __slots__, etc. is this from the python reference.
I have a Python class
class pytest:
i = 34
def func(self):
return "hello world"
When I access pytest.i, I get 34. I can also do this another way:
a = pytest()
a.i
This gives 34 as well.
If I try to access the (non-existing) pytest.j, I get
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
pytest.j
AttributeError: class pytest has no attribute 'j'
while when I try a.j, the error is
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
a.j
AttributeError: pytest instance has no attribute 'j'
So my question is: What exactly happens in the two cases and what is the difference?
No, these are two different things.
In Python, everything is an object. Classes are objects, functions are objects and instances are objects. Since everything is an object, everything behaves in a similar way. In your case, you create a class instance (== an object with the type "Class") with the name "pytest". That object has two attributes: i and fuc. i is an instance of "Integer" or "Number", fuc is an instance of "Function".
When you use "pytest.j", you tell python "look up the object pytest and when you have it, look up i". "pytest" is a class instance but that doesn't matter.
When you create an instance of "pytest" (== an object with the type "pytest"), then you have an object which has "defaults". In your case, a is an instance of pytest which means that anything that can't be found in a will be searched in pytest, next.
So a.j means: "Look in a. When it's not there, also look in pytest". But j doesn't exist and Python now has to give you a meaningful error message. It could say "class pytest has no attribute 'j'". This would be correct but meaningless: You would have to figure out yourself that you tried to access j via a. It would be confusing. Guido won't have that.
Therefore, python uses a different error message. Since it does not always have the name of the instance (a), the designers decided to use the type instead, so you get "pytest instance...".
To summarize, there are two types of variables associated with classes and objects: class variables and instance variables. Class variables are associated with classes, but instance variables are associated with objects. Here's an example:
class TestClass:
classVar = 0
def __init__(self):
self.instanceVar = 0
classVar is a class variable associated with the class TestClass. instanceVar is an instance variable associated with objects of the type TestClass.
print(TestClass.classVar) # prints 0
instance1 = TestClass() # creates new instance of TestClass
instance2 = TestClass() # creates another new instance of TestClass
instance1 and instance2 share classVar because they're both objects of the type TestClass.
print(instance1.classVar) # prints 0
TestClass.classVar = 1
print(instance1.classVar) # prints 1
print(instance2.classVar) # prints 1
However, they both have copies of instanceVar because it is an instance variable associated with individual instances, not the class.
print(instance1.instanceVar) # prints 0
print(TestClass.instanceVar) # error! instanceVar is not a class variable
instance1.instanceVar = 1
print(instance1.instanceVar) # prints 1
print(instance2.instanceVar) # prints 0
As Aaron said, if you try to access an instance variable, Python first checks the instance variables of that object, then the class variables of the object's type. Class variables function as default values for instance variables.