How do I block access to setting parameters in a class?

Asked

Viewed 280 times

4

import math

class Circulo():

    def __init__(self):
        super()
        self.__raio = None

    def get_perimetro(self):
        return 2 * math.pi * self.raio

    def get_area(self):
        return math.pi * self.raio ** 2

    @property
    def raio(self):
        return self.__raio

    @raio.setter
    def raio(self, x):
        self.__raio = x

I have the above class and wish to encapsulate the access so that dynamic attributes in the instance are not possible.

ex:

c = Circulo()
c.raio = 2 # ok
c.lado = 2 # AttributeError

I tried to block dynamic attributes with getattr and setattr, but I was unsuccessful.

def __getattr__(self, item):
    if item in self.__dict__:
        return self.__dict__[item]
    else:
        raise AttributeError('Paramentro ou atributo "%s" inexistente.' % item)

def __setattr__(self, key, value):
    if key in self.__dict__:
        self.__dict__[key] = value
    else:
        raise AttributeError('Paramentro ou atributo "%s" inexistente.' % key)

3 answers

5

What you want to do is clearly function of __slots__, vine documentation:

Without a __dict__ variable, instances cannot be Assigned new variables not Listed in the __slots__ Definition. Attempts to assign to an unlisted variable name raises AttributeError.

That is, if you want only the attribute __raio exist, just put in the class:

__slots__ = ("__raio")

Take the example:

import math

class Circulo():

    __slots__ = ("__raio")

    def __init__(self):
        super()
        self.__raio = None

    def get_perimetro(self):
        return 2 * math.pi * self.raio

    def get_area(self):
        return math.pi * self.raio ** 2

    @property
    def raio(self):
        return self.__raio

    @raio.setter
    def raio(self, x):
        self.__raio = x

c = Circulo()
c.raio = 2 # ok
c.lado = 2 # AttributeError

See working on Ideone.

The result will be:

Traceback (most recent call last):
  File "./prog.py", line 27, in <module>
AttributeError: 'Circulo' object has no attribute 'lado'

Remembering that as __raio belongs to the list defined in __slots__, the exception is not triggered in the class initializer when defining the instance attribute self.__raio = None. By not setting the list correctly, an exception can be triggered within the class itself.

  • Thanks @Anderson Carlos Woss, thank you so much!!! I’m going to study this one slots now!! ;D

  • Anderson Carlos Woss, + a doubt. @Property becomes unnecessary?

  • That’s for you to say. As yours get/set only return and assign value without any business rule, yes, it is unnecessary. You can only use the attribute as "public".

  • Thanks Anderson. Thank you very much. I expanded my knowledge with your help.

4

Try this:

import math

class Circulo():
    def __init__(self):
        super()
        self.__raio = None

    def get_perimetro(self):
        return 2 * math.pi * self.raio

    def get_area(self):
        return math.pi * self.raio ** 2

    @property
    def raio(self):
        return self.__raio

    @raio.setter
    def raio(self, x):
        self.__raio = x

    def __setattr__(self, key, value):
        if not hasattr(self, key):
            raise TypeError("Não pode criar atributos para esta classe")
        object.__setattr__(self, key, value)

c = Circulo()
c.raio = 2 # ok
c.lado = 2 # AttributeError

Behold "working" in the ideone. And in the repl it.. Also put on the Github for future reference.

  • Unfortunately it still does not fall into the exec: Attributeerror: Non-existent "_Circulo__radius" parameter or attribute.

  • @britodfbr works for me: http://ideone.com/cbZdJh

  • Yes, but the error was accused in <c.radius>. And should be charged only in <c.lado>. I made a Fork of your code in http://ideone.com/BClC2X, I added only the key to show where the error occurs.

  • Maybe use __slots__ is more appropriate: https://answall.com/a/221432/5878

  • @Andersoncarloswoss very possible.

2

The ending with the help of Anderson Woss:

import math

class FormaGeometrica:
    __slots__ = ()
    def get_area(self):
        raise NotImplementedError(NotImplemented)
    def get_perimetro(self):
        raise NotImplementedError(NotImplemented)


class Circulo(FormaGeometrica):
    __slots__ = ("raio")

    def __init__(self):
        super()
        self.raio = None

    def get_perimetro(self):
        return 2 * math.pi * self.raio

    def get_area(self):
        return math.pi * self.raio ** 2


if __name__ == '__main__':
    c = Circulo()
    c.raio = 2  # ok
    c.get_area()
    c.get_perimetro()
    c.lado = 2  # AttributeError

Previously, to be able to implement the class by restricting dynamic creation of attributes and methods, I had to rewrite the setattr, getattr and control the Dict class.

The final solution was like the code below. If anyone had any suggestions for improvement, they’d be welcome.

from incolumepy.geometria.figura_geometrica import FiguraGeometrica
import math


class Circulo(FiguraGeometrica):
    '''
    >>> c = Circulo()
    >>> c.raio = 1
    >>> c.raio
    1
    >>> c.lado = 2
    Paramentro ou atributo "lado" inexistente.
    '''
    __dict__ = {'raio': None}

    def get_perimetro(self):
        return 2 * math.pi * self.raio

    def get_area(self):
        return math.pi * self.raio ** 2

    def __getattr__(self, item):
        if item not in self.__dict__:
            raise AttributeError('Paramentro ou atributo "%s" inexistente.' % item)
        return self.__dict__[item]

    def __setattr__(self, key, value):
        if key not in self.__dict__:
            raise AttributeError('Paramentro ou atributo "{}" inexistente.'.format(key))
        self.__dict__[key] = value


if __name__ == '__main__':
    import doctest
    doctest.testmod()
  • In this case you have given up using the property?

  • That’s what I was able to do. I didn’t know slots.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.