Forbid to add attributes to user-defined class in Python - python

When you create a user-defined class, you can, by default, dinamically add attributes to it just like in the next example:
# User defined class
class Test:
def __init__(self):
self.first_att = 5
test = Test()
test.second_att = 11
print(test.__dict__)
{'first_att': 5, 'second_att': 11}
But built-in classes don't allow such a thing:
# Built-in class
str_example = 'Test string'
str_example.added_att = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'added_att'
How can I modify my class Test so it doesn't allow it also?
Thanks in advance

You can define in your class the allowed attributes by adding __slots__.
In there put the names of the attributes you want. All other attributes won't be accepted.
For example try this:
# User defined class
class Test:
__slots__ = ("fitst_att", "your_att_name")
def __init__(self):
self.first_att = 5
test = Test()
test.your_att_name = 11
print(test.__dict__)
# test.second_att = 11 ##this will rais an error

You can achieve that by overwriting the __setattr__ method. As a very simple example for your case
class Test:
def __init__(self):
self.first_att = 5
def __setattr__(self, key, value):
if key not in ("first_att",): # allowed attributes
raise AttributeError(f"Not allowed to set attribute {key}")
super().__setattr__(key, value)
Note that you have to add an exception list here that holds the names of the attributes you want to be able to assign values to.

First preventing the user to do a harmless thing is uncommon in Python. Builtin classes do no willingly forbid to add attributes, they simply have static slots instead of a dynamic __dict__ to store their members, because it is simpler to do that when writing a Python class using the C API.
You can mimic that with Python classes by using a __slots__ attribute. But beware, it is not really a security thing, because by default subclasses will receive a __dict__ member and will allow dynamic attributes.
Demo:
class Test:
__slots__ = ['first_att']
def __init__(self):
self.first_att = 5
test = Test()
test.second_att = 7
Traceback (most recent call last):
...
AttributeError: 'Test' object has no attribute 'second_att'
But:
class TestChild(Test):
pass
child = TestChild()
child.second_att = 7
without any exception...

Related

Initialize object properties as class properties first in Python

I have the habit to initialize the properties of an instance of a class in the constructor of that class but, in case the properties are very tight to the class, I also declare them and initialize them to None ([] or some other base value) as properties in the class.
For instance:
class AClass(object):
aprop = None
def __init__(self):
self.aprop = "avalue"
Which in most of the cases it won't make much of a difference from just doing:
class AClass(object):
def __init__(self):
self.aprop = "avalue"
However, if somebody gets the scope of the AClass will notice that an instance of this class is expected to have an attribute named aprop. I think of it as a placeholder for the property aprop in the class.
This looks to me more as a question of style, but I would like to know whether this is a good practice. Would you recommend it? Is it something common? or should I try to get rid of it?
When you do self.aprop = "avalue" in the __init__ function, every instance of AClass will have the aprop property since __init__ is called when you initiate an instance. This makes aprop an instance variable.
When you do aprop = None you add a class variable to AClass. This means that the class itself will have this property.
For Example:
>>> class AClass(object):
... def __init__(self):
... self.aprop = "avalue"
...
>>> AClass.aprop
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'AClass' has no attribute 'aprop'
And:
>>> class AClass(object):
... aprop = None
... def __init__(self):
... self.aprop = "avalue"
...
>>> AClass.aprop
>>> print AClass.aprop
None
So if you want your class to have this property you should define it as a class variable. If you only use it in instances, the only case you should define a class property is if you don't always redefine it (hide it) in __init__:
class AClass(object):
aprop = None
def __init__(self):
self.bprop = "bvalue"
Unless you ever access the property on the class itself (e.g. as AClass.aprop) rather than on an instance, there's no need to make it a class member if you always populate it on an instance in the __init__ method.
My vote is to always write the least amount of code necessary to clearly convey the intent. Adding an extra member to the class obscures the intent (since your actual goal here is for it to be an instance property) and is unnecessary: that sounds like two strikes against it.

Why object's attributes are not there when the class is instantiated?

Why instantiating a class does not instantiate all its attributes ? I think a very simple example could explain my problem better:
class Example:
def example(self):
self.var=10
if __name__=='__main__':
E=Example()
# In this case, the attribute var is not instantiated
try:
attr=getattr(E,"var")
print(attr) # <-- it does not exist even if E instantiated Example class
except AttributeError:
print("Object not having this attribute") # <-- This is the output
Why the object E does not have all its attributes instantiated (namely the attribute var) ?
Unlike Java, in Python, the initiator function is called __init__; naming the method like the name of the class does not create a constructor.
So, when you instantiate an object of class Example, the method example is not called and your attribute doesn't exist. You'll have to call it explicitly, like this:
>>> e = Example()
>>> e.example()
>>> e.var
10
To have the attribute available to all objects at instantiation, modify your class and create a __init__ method, like this:
class Example:
def __init__(self):
self.var = 10
Now, it will work as you expect:
>>> e = Example()
>>> e.var
10
in your class if you need to initialize var you need to call it explicitly by calling the example method in the example class
but if if you write __init__ method it it automatically initialize the variable at the time of object creation
The init method (init for initialise) is called when the object is instantiated. Instantiation is done by (effectively) calling the
class.Here a new instance is created. Then its init method is called .it call the example method to initialize var in the exapme
class Example:
def __init__(self):
self.var=0 # or you can directly give self.var=10
self.example()
def example(self):
self.var=10
if __name__=='__main__':
E=Example()
# In this case, the attribute var is not instantiated
try:
attr=getattr(E,"var")
print(attr) # <-- it does not exist even if E instantiated Example class
except AttributeError:
print("Object not having this attribute")
other way of doing is
when you intalized class object call the method in it
E=Example()
E.example()#explicitly call example method in the class
You not called example method which sets var to self.
try
if __name__=='__main__':
E=Example()
E.example()
# In this case, the attribute var is not instantiated
try:
attr=getattr(E,"var")
print(attr)
except AttributeError:
print("Object not having this attribute")

When setting data fields in Python objects, should I use the "self" keyword?

Should I write
class MyClass:
def __init__(self):
self.field = 0
or
class MyClass:
def __init__(self):
field = 0
Is there any difference between these statements?
Yes, you must use the self variable to set the properties of Python objects/instances.
In the second case, you're just creating a local variable in __init__, not defining a property.
class MyClass:
def __init__(self):
field = 0
print MyClass().field # an error! it's not defined!
You may be misled by the way that you can just use raw assignments in class blocks to set class properties:
class MyClass:
a = 2
print MyClass.a # 2
The behaviour in class blocks is unusual, and you don't get similar behaviour inside methods.
The first chunk of code creates an instance attribute that can be accessed once an instance of the class has been created:
>>> a = MyClass()
>>> a.field
0
The scope of the second chunk's field variable is only within the __init__ function, so accessing the variable outside of that scope won't work:
>>> a = MyClass()
>>> a.field
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: MyClass instance has no attribute 'field'
The former sets the value as a property of the class. The latter, as simply a local variable.
If elsewhere in your program you do
foo = MyClass();
print foo.field;
The print statement above will only work with the first code snippet, not with the second.

How to restrict setting an attribute outside of constructor?

I want to forbid further assignments on some attributes of a class after it was initialized. For instance; no one can explicitly assign any value to 'ssn' (social security number) property after the Person instance 'p' has been initialized. _setattr_ is also being called while assigning the value inside _init_ method, thus it is not what I want. I'd like to restrict only further assignments. How can I achieve that?
class Person(object):
def __init__(self, name, ssn):
self.name = name
self._ssn = ssn
def __setattr__(self, name, value):
if name == '_ssn':
raise AttributeError('Denied.')
else:
object.__setattr__(self, name, value)
>> p = Person('Ozgur', '1234')
>> AttributeError: Denied.
The usual way is to use a "private" attribute starting with an underscore, and a read-only property for public access:
import operator
class Person(object):
def __init__(self, name, ssn):
self.name = name
self._ssn = ssn
ssn = property(operator.attrgetter("_ssn"))
Note that this does not really hinder anybody to change the attribute _ssn, but the leading _ documents that the attribute is private.
Python does not support private or protected attributes. You need to implement the descriptor protocol instead. The standard library provides decorators to do that succinctly.
Just declare the attribute with two underscores in front of it in the init method. It is called name mangling and prevents the attribute from being accessible via __ssn, although it can still be accessed and modified by _Person__ssn in this case. However, if you do not explicitly define a setter for it will raise an AttributeError.
Of course if someone has an intention to misuse the API that person can if he is very intent. But it will not happen by accident.
import re
class Person:
"""Encapsulates the private data of a person."""
_PATTERN = re.COMPILE(r'abcd-efgh-ssn')
def __init__(self, name, ssn):
"""Initializes Person class with input name of person and
his social security number (ssn).
"""
# you can add some type and value checking here for defensive programming
# or validation for the ssn using regex for example that raises an error
if not self._PATTERN.match(ssn):
raise ValueError('ssn is not valid')
self.__name = name
self.__ssn = snn
#property
def name(self):
return self.__name
#name.setter
def name(self, value):
self.__name = value
#property
def ssn(self):
return self.__ssn
>>> p = Person('aname', 'abcd-efgh-ssn')
>>> p.ssn
'abcd-efgh-ssn'
>>> p.ssn = 'mistake'
AttributeError: 'can't set attribute'
Just pointing out that we could still modify _ssn.
Objects have the special attribute, __dict__ that is a dictionary that maps all instance attributes of the object with their corresponding values. We can add/update/delete instance attributes directly by modifying the __dict__ attribute of an object.
We can still modify _snn like this:
p = Person('Ozgur', '1234')
p.__dict__.get('_ssn') # returns '1234'
p.__dict__['_ssn'] = '4321'
p.__dict__.get('_ssn') # returns '4321'
As we can see, we were still able to change the value of _ssn. By design, there isn't a straight forward way, if any, to circumvent attribute access in Python in all cases.
I'll show a more common way to restrict attribute access using property() as a decorator:
class Person(object):
def __init__(self, name, ssn):
self.name = name
self._ssn = ssn
#property
def ssn(self):
return self._ssn
#ssn.setter
def ssn(self, value):
raise AttributeError('Denied')
>> p = Person('Ozgur', '1234')
>> p.ssn
>> '1234'
>> p.ssn = '4321'
>> AttributeError: Denied
Hope this helps!
You can bypass setattr by directly assigning to the instance dictionary in the constructor. Of course, this trick can always be used to spoof any other hoops you employ to make _ssn read-only (after an initial write)
class Person(object):
def __init__(self, name, ssn):
self.name = name
self.__dict__['_ssn'] = ssn
def __setattr__(self, name, value):
if name == '_ssn':
raise AttributeError('Denied.')
else:
object.__setattr__(self, name, value)
Problem 📖
In Python you can not create a true read-only attribute, but you can make modifying it harder.
You can make modifying an attribute a true headache even with Python. Here is the recipe. Even if you were willing to go to C-extension level, modifications to attributes could still be done but they would require using C pointers.
Solution 🍝
from types import MappingProxyType as Proxy
class Person:
"""A person with two read-only attributes.
Setting __slots__ will prevent adding new attributes, but __slots__ itself
can still be modified. Custom __setattr__ will prevent direct modification
of __slots__ while custom __getattr__ will direct attribute lookup towards
the real keys hiding behind the proxy attribute. Then, MappingProxyType is
used to prevent easily modifying the proxy attribute even when accessed by
using object.__getattribute__ and object.__setattr__ to bypass the methods
defined in 'read-only' class. Normal 'self.proxy' can not be used here due
to the __getattr__ and __setattr__ preventing it.
"""
__slots__ = ("proxy")
def __init__(self, name, ssn):
object.__setattr__(self, "proxy", Proxy({"name": name, "ssn": ssn}))
def __getattr__(self, name):
proxy = object.__getattribute__(self, "proxy")
if (name == "proxy") or (name not in proxy):
return object.__getattribute__(self, name)
return proxy[name]
def __setattr__(self, _name, _val):
raise AttributeError("Read-only object")
Example 🖥️
>>> person = Person("Joe", 123)
>>>
>>> person.name, person.ssn
('Joe', 123)
>>>
>>> person.name, person.ssn = "Niinistö", 1337
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 22, in __setattr__
AttributeError: Read-only object
>>>
>>>
>>> person.fake_attribute = 1337
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 22, in __setattr__
AttributeError: Read-only object
>>>

How do I set and access attributes of a class? [duplicate]

This question already has answers here:
How can I access "static" class variables within methods?
(6 answers)
Closed 12 days ago.
Suppose I have this code:
class Example(object):
def the_example(self):
itsProblem = "problem"
theExample = Example()
print(theExample.itsProblem)
When I try it, I get an error that says:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Example' object has no attribute 'itsProblem'
How do I access this attribute? I tried adding another method to return it:
def return_itsProblem(self):
return itsProblem
but the problem persists.
The answer, in a few words
In your example, itsProblem is a local variable.
Your must use self to set and get instance variables. You can set it in the __init__ method. Then your code would be:
class Example(object):
def __init__(self):
self.itsProblem = "problem"
theExample = Example()
print(theExample.itsProblem)
But if you want a true class variable, then use the class name directly:
class Example(object):
itsProblem = "problem"
theExample = Example()
print(theExample.itsProblem)
print (Example.itsProblem)
But be careful with this one, as theExample.itsProblem is automatically set to be equal to Example.itsProblem, but is not the same variable at all and can be changed independently.
Some explanations
In Python, variables can be created dynamically. Therefore, you can do the following:
class Example(object):
pass
Example.itsProblem = "problem"
e = Example()
e.itsSecondProblem = "problem"
print Example.itsProblem == e.itsSecondProblem
prints
True
Therefore, that's exactly what you do with the previous examples.
Indeed, in Python we use self as this, but it's a bit more than that. self is the the first argument to any object method because the first argument is always the object reference. This is automatic, whether you call it self or not.
Which means you can do:
class Example(object):
def __init__(self):
self.itsProblem = "problem"
theExample = Example()
print(theExample.itsProblem)
or:
class Example(object):
def __init__(my_super_self):
my_super_self.itsProblem = "problem"
theExample = Example()
print(theExample.itsProblem)
It's exactly the same. The first argument of ANY object method is the current object, we only call it self as a convention. And you add just a variable to this object, the same way you would do it from outside.
Now, about the class variables.
When you do:
class Example(object):
itsProblem = "problem"
theExample = Example()
print(theExample.itsProblem)
You'll notice we first set a class variable, then we access an object (instance) variable. We never set this object variable but it works, how is that possible?
Well, Python tries to get first the object variable, but if it can't find it, will give you the class variable. Warning: the class variable is shared among instances, and the object variable is not.
As a conclusion, never use class variables to set default values to object variables. Use __init__ for that.
Eventually, you will learn that Python classes are instances and therefore objects themselves, which gives new insight to understanding the above. Come back and read this again later, once you realize that.
You are declaring a local variable, not a class variable. To set an instance variable (attribute), use
class Example(object):
def the_example(self):
self.itsProblem = "problem" # <-- remember the 'self.'
theExample = Example()
theExample.the_example()
print(theExample.itsProblem)
To set a class variable (a.k.a. static member), use
class Example(object):
def the_example(self):
Example.itsProblem = "problem"
# or, type(self).itsProblem = "problem"
# depending what you want to do when the class is derived.
If you have an instance function (i.e. one that gets passed self) you can use self to get a reference to the class using self.__class__
For example in the code below tornado creates an instance to handle get requests, but we can get hold of the get_handler class and use it to hold a riak client so we do not need to create one for every request.
import tornado.web
import riak
class get_handler(tornado.web.requestHandler):
riak_client = None
def post(self):
cls = self.__class__
if cls.riak_client is None:
cls.riak_client = riak.RiakClient(pb_port=8087, protocol='pbc')
# Additional code to send response to the request ...
Implement the return statement like the example below! You should be good. I hope it helps someone..
class Example(object):
def the_example(self):
itsProblem = "problem"
return itsProblem
theExample = Example()
print theExample.the_example()
If you have a #classmethod static method, you always have the class as the first parameter:
class Example(object):
itsProblem = "problem"
#classmethod
def printProblem(cls):
print(cls.itsProblem)
Example.printProblem()

Categories

Resources