associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

PrivaiMe

Você, que acha que toda linguagem OO tem de ter variáveis privadas porque aprendeu com uma que tem?

Acha que isso é um mandamento sagrado e acha que Python é uma linguagem pecadora porque não tem?

Suas orações foram atendidas. Usando o código abaixo você estará privado de todo o mal dos atributos públicos.

Exceto para alguém que esteja REALMENTE disposto a alterá-los, e esse merece ir para o inferno, não? Não? Não acha?

Bom... então é melhor deixar isso pra lá e não continuar lendo porque talvez você acabe levando isso a sério.

Basta salvar o código abaixo em um módulo, importar a função private para seu módulo e definir a variável '__metaclass__ = private('var1', 'var2', 'var3', ...)  no início, listando todos os atributos que deseja deixar privados.

OBS: aos desavisados, esse código, nos moldes do NoSelf, também é uma brincadeira, feita para mostrar como atributos privados não são uma prioridade e que em Python, se você estiver realmente disposto, é possível restringir o acesso cada vez mais e mais (e aqui nem foi muito longe), mas se o usuário quer mesmo alterar, por que impedir?

Crédito ao João S. O. Bueno pela idéia original e algumas dicas. Ele também encontrou algumas maneiras de quebrar (que não mencionarei aqui), e para cada uma delas até encontrei uma maneira de proteger, mas acho que isso já basta para ilustrar a idéia...

#!/usr/bin/env python
# encoding: utf-8

import inspect
import sys
import types
import unittest


class PrivateAttributeError(AttributeError):
    pass


def _check_caller(obj, level):
    frame = sys._getframe(level)
    cls = type(obj)

    values = frame.f_locals.values()
    if obj not in values:
        return False

    funcs = [func for func in cls.__dict__.values() if isinstance(func, types.FunctionType)]

    for func in funcs:
        code1 = func.func_code
        code2 = frame.f_code
        if code1 is code2:
            break
    else:
        return False
    return True


class Private(object):
    pass


class PrivateAttribute(Private):
    def __init__(self, name):
        self.name = name

    def __get__(self, obj, cls=None):
        if not _check_caller(obj, 2):
            raise PrivateAttributeError("can only be used inside a method")
        try:
            value = obj.__privdict__[self.name]
        except KeyError:
            raise AttributeError("Private attribute '%s' not set" % self.name)
        return value

    def __set__(self, obj, value):
        if not _check_caller(obj, 2):
            raise PrivateAttributeError("can only be used inside a method")
        obj.__privdict__[self.name] = value

    def __delete__(self, obj):
        if not _check_caller(obj, 2):
            raise PrivateAttributeError("can only be used inside a method")
        try:
            del obj.__privdict__[self.name] 
        except KeyError:
            raise AttributeError("Private attribute '%s' not set" % self.name)


class PrivateMethod(Private):
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, cls=None):
        if not _check_caller(obj, 2):
            raise PrivateAttributeError("can only be used inside a method")
        return types.MethodType(self.func, obj, cls)

    def __set__(self, obj, value):
        raise RuntimeError("You can't reasign a private method")

    def __delete__(self, obj):
        raise RuntimeError("You can't reasign a private method")
    
    
class PrivateDict(object):
    __slots__ = ['owner', '__privdict__']
    def __init__(self, owner):
        self.owner = owner
        self.__privdict__ = {}
        
    def __setitem__(self, key, value):
        if not _check_caller(self.owner, 3):
            raise PrivateAttributeError("Are you trying to break this?")
        self.__privdict__[key] = value

    def __getitem__(self, key):
        if not _check_caller(self.owner, 3):
            raise PrivateAttributeError("Are you trying to break this?")
        return self.__privdict__[key]

    def __getattribute__(self, attr):
        if not _check_caller(self, 2):
            raise PrivateAttributeError("Are you trying to break this?")
        return super(PrivateDict, self).__getattribute__(attr)

    def __setattribute__(self, attr):
        if not _check_caller(self, 2):
            raise PrivateAttributeError("Are you trying to break this?")
        return super(PrivateDict, self).__getattribute__(attr)
    

class MetaEnablePrivate(type):
    def __call__(cls, *args, **kwds):
        new = cls.__new__(cls, *args, **kwds)
        new.__privdict__ = PrivateDict(new)
        cls.__init__(new, *args, **kwds)
        return new


def private(*args):
    attrs = dict((name, PrivateAttribute(name)) for name in args)

    def _metaclass(name, bases, dict):
        dict.update(attrs)
        return MetaEnablePrivate(name, bases, dict)

    return _metaclass


# Um testezinho...
class Test(object):
    __metaclass__ = private('foo', 'bar')
    
    def __init__(self, name):
        self.foo = None
        self.bar = None

        self.name = name
        self.wow = None

    def set_foo(self, value):
        self.foo = value
        self.bar = "and I am new bar in %s"%self.name

    def get_foo(self):
        return self.foo

    @PrivateMethod
    def meth(self):
        return 'I am supposed to be a private method'

    def call_meth(self):
        return self.meth()


class TestPrivateAttributes(unittest.TestCase):
    def setUp(self):
        self.obja = Test('a')
        self.objb = Test('b')
        self.obja.set_foo('I am foo in a')
        self.objb.set_foo('I am foo in b')

    def testGettersSetters(self):
        # get original
        self.assertEqual(self.obja.get_foo(), 'I am foo in a')
        self.assertEqual(self.objb.get_foo(), 'I am foo in b')
        # set
        self.obja.set_foo('I am new foo in a')
        self.objb.set_foo('I am new foo in b')
        # check if it really got set
        self.assertEqual(self.obja.get_foo(), 'I am new foo in a')
        self.assertEqual(self.objb.get_foo(), 'I am new foo in b')        

    def testBreakDirectGet(self):
        self.assertRaises(PrivateAttributeError, getattr, self.obja, 'foo')
        self.assertRaises(PrivateAttributeError, getattr, self.obja, 'bar')
        self.assertEqual(self.obja.wow, None)
        self.assertRaises(PrivateAttributeError, getattr, self.objb, 'foo')
        self.assertRaises(PrivateAttributeError, getattr, self.objb, 'bar')
        self.assertEqual(self.objb.wow, None)

    def testBreakDirectSet(self):
        self.assertRaises(PrivateAttributeError, setattr, self.obja, 'foo', '')
        self.assertRaises(PrivateAttributeError, setattr, self.obja, 'bar', '')
        self.obja.wow = ''
        self.assertRaises(PrivateAttributeError, setattr, self.objb, 'foo', '')
        self.assertRaises(PrivateAttributeError, setattr, self.objb, 'bar', '')
        self.objb.wow = ''

    def testWithObjectGetattribute(self):
        self.assertRaises(PrivateAttributeError, object.__getattribute__,
                          self.obja, 'foo')
        self.assertRaises(PrivateAttributeError, object.__getattribute__,
                          self.obja, 'bar')
        self.assertRaises(PrivateAttributeError, object.__getattribute__,
                          self.objb, 'foo')
        self.assertRaises(PrivateAttributeError, object.__getattribute__,
                          self.objb, 'bar')

    def testWithObjectSetattr(self):
        self.assertRaises(PrivateAttributeError, object.__setattr__,
                          self.obja, 'foo', '')
        self.assertRaises(PrivateAttributeError, object.__setattr__,
                          self.obja, 'bar', '')
        self.assertRaises(PrivateAttributeError, object.__setattr__,
                          self.objb, 'foo', '')
        self.assertRaises(PrivateAttributeError, object.__setattr__,
                          self.objb, 'bar', '')

    def testWithObjectPrivdict(self):
        self.assertRaises(PrivateAttributeError, lambda :self.obja.__privdict__['foo'])
        self.assertRaises(PrivateAttributeError, lambda :self.obja.__privdict__['bar'])
        self.assertRaises(PrivateAttributeError, lambda :self.objb.__privdict__['foo'])
        self.assertRaises(PrivateAttributeError, lambda :self.objb.__privdict__['bar'])



if __name__ == '__main__':
    unittest.main()