Created
February 6, 2021 23:01
-
-
Save JoaoFelipe/3c146bda9ddd53d0b85a4aad75760da7 to your computer and use it in GitHub Desktop.
Metaclass for private attributes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"id": "modern-laundry", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import types\n", | |
"from functools import wraps\n", | |
"from copy import copy\n", | |
"\n", | |
"def private_context(method):\n", | |
" @wraps(method)\n", | |
" def wrapper(self, *args, **kwargs):\n", | |
" try:\n", | |
" object.__setattr__(self, '__private_context__', True)\n", | |
" return method(self, *args, **kwargs)\n", | |
" finally:\n", | |
" object.__setattr__(self, '__private_context__', False)\n", | |
" return wrapper\n", | |
"\n", | |
"\n", | |
"class PrivateProperty(property):\n", | |
" \n", | |
" def __init__(self, prop):\n", | |
" fget = private_context(prop.fget) if prop.fget else None\n", | |
" fset = private_context(prop.fset) if prop.fset else None\n", | |
" fdel = private_context(prop.fdel) if prop.fdel else None\n", | |
" doc = prop.__doc__\n", | |
" \n", | |
" super().__init__(fget, fset, fdel, doc)\n", | |
" \n", | |
" \n", | |
"class MetaPrivate(type):\n", | |
" \"\"\"Allows the definition of private methods\n", | |
" It has two modes of operation:\n", | |
" If you set __private__ as a list of private attributes, \n", | |
" the only private methods and attributes are the ones in the list\n", | |
" If you set __public__ as a list of public attributes, \n", | |
" all attributes and methods become private, except the ones in the list\n", | |
" \"\"\"\n", | |
" def __new__(cls, name, bases, dct):\n", | |
" if '__private__' in dct or '__public__' in dct:\n", | |
" private = set(dct.get('__private__', []))\n", | |
" public = set(dct.get('__public__', []))\n", | |
" new_dct = copy(dct)\n", | |
" def accessible_key(self, key):\n", | |
" is_public = key in public\n", | |
" is_private = '__public__' in dct or key in private\n", | |
" in_private_context = object.__getattribute__(self, '__private_context__')\n", | |
" return is_public or not is_private or in_private_context\n", | |
" def __getattribute__(self, key):\n", | |
" if accessible_key(self, key):\n", | |
" return object.__getattribute__(self, key)\n", | |
" raise AttributeError(f'Attempt to access private attribute \"{key}\"')\n", | |
" def __setattr__(self, key, value):\n", | |
" if accessible_key(self, key):\n", | |
" return object.__setattr__(self, key, value)\n", | |
" raise AttributeError(f'Attempt to set private attribute \"{key}\"')\n", | |
" \n", | |
" new_dct['__private_context__'] = False\n", | |
" new_dct['__getattribute__'] = __getattribute__\n", | |
" new_dct['__setattr__'] = __setattr__\n", | |
" \n", | |
" for key, value in dct.items():\n", | |
" if isinstance(value, types.FunctionType):\n", | |
" new_dct[key] = private_context(value)\n", | |
" if isinstance(value, property):\n", | |
" new_dct[key] = PrivateProperty(value)\n", | |
" \n", | |
" dct = new_dct\n", | |
" result = super().__new__(cls, name, bases, dct)\n", | |
" return result\n", | |
" " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "aquatic-killing", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"MyShyPrivate\n", | |
"spam\n", | |
"Falhou ao tentar acessar o atributo \"private\"\n", | |
"Falhou ao tentar criar o atributo \"private\"\n", | |
"eggs\n", | |
"MyShyPublic\n", | |
"spam\n", | |
"Falhou ao tentar acessar o atributo \"private\"\n", | |
"Falhou ao tentar criar o atributo \"private\"\n", | |
"eggs\n" | |
] | |
} | |
], | |
"source": [ | |
"def exemplo1(cls):\n", | |
" print(cls.__name__)\n", | |
" # Uma instência envergonhada.\n", | |
" my_shy = cls()\n", | |
"\n", | |
" # Ok, mostre o valor do seu atributo \"private\".\n", | |
" my_shy.show_private()\n", | |
"\n", | |
" # Mas podemos olhar diretamente o seu atributo \"private\"?\n", | |
" try:\n", | |
" # Erro de atributo: para código externo, my_shy não tem atributo \"private\".\n", | |
" print(my_shy.private)\n", | |
"\n", | |
" except AttributeError:\n", | |
" print('Falhou ao tentar acessar o atributo \"private\"')\n", | |
"\n", | |
" # Então podemos criar em você um atributo \"private\"?\n", | |
" try:\n", | |
" # Erro de atributo: código externo não pode criar atributos em my_shy.\n", | |
" my_shy.private = \"eggs\"\n", | |
"\n", | |
" except AttributeError:\n", | |
" print('Falhou ao tentar criar o atributo \"private\"')\n", | |
"\n", | |
" # Ok, altere você mesmo o valor do seu atributo \"private\".\n", | |
" my_shy.set_private()\n", | |
"\n", | |
" # Agora, mostre o novo valor do seu atributo \"private\".\n", | |
" my_shy.show_private()\n", | |
"\n", | |
"class MyShyPrivate(metaclass=MetaPrivate):\n", | |
" __private__ = [\"private\"]\n", | |
" \n", | |
" def __init__(self):\n", | |
" self.private = \"spam\"\n", | |
" \n", | |
" def show_private(self):\n", | |
" print(self.private)\n", | |
" \n", | |
" def set_private(self):\n", | |
" self.private = \"eggs\"\n", | |
" \n", | |
"exemplo1(MyShyPrivate)\n", | |
"\n", | |
"class MyShyPublic(metaclass=MetaPrivate):\n", | |
" __public__ = [\"show_private\", \"set_private\"]\n", | |
" \n", | |
" def __init__(self):\n", | |
" self.private = \"spam\"\n", | |
" \n", | |
" def show_private(self):\n", | |
" print(self.private)\n", | |
" \n", | |
" def set_private(self):\n", | |
" self.private = \"eggs\"\n", | |
"\n", | |
"exemplo1(MyShyPublic)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"id": "musical-merchandise", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"MyShyPrivate\n", | |
"spam\n", | |
"Falhou ao tentar criar o atributo \"read_only\"\n", | |
"Falhou ao tentar acessar o atributo interno \"data\"\n", | |
"MyShyPublic\n", | |
"spam\n", | |
"Falhou ao tentar criar o atributo \"read_only\"\n", | |
"Falhou ao tentar acessar o atributo interno \"data\"\n" | |
] | |
} | |
], | |
"source": [ | |
"def exemplo2(cls):\n", | |
" print(cls.__name__)\n", | |
" # Uma instência envergonhada.\n", | |
" my_shy = cls()\n", | |
"\n", | |
" # Podemos olhar diretamente o seu atributo \"read_only\"?\n", | |
" print(my_shy.read_only)\n", | |
"\n", | |
" # Mas podemos alterar o seu atributo \"read_only\"?\n", | |
" try:\n", | |
" # Erro de atributo: código externo não pode criar atributos em my_shy.\n", | |
" my_shy.read_only = \"eggs\"\n", | |
"\n", | |
" except AttributeError:\n", | |
" print('Falhou ao tentar criar o atributo \"read_only\"')\n", | |
"\n", | |
" # Mas podemos olhar diretamente o seu atributo interno \"data\"?\n", | |
" try:\n", | |
" # Erro de atributo: para código externo, my_shy não tem atributo \"data\".\n", | |
" print(my_shy.data)\n", | |
"\n", | |
" except AttributeError:\n", | |
" print('Falhou ao tentar acessar o atributo interno \"data\"')\n", | |
"\n", | |
"\n", | |
"class MyShyPrivate(metaclass=MetaPrivate):\n", | |
" __private__ = ['data']\n", | |
"\n", | |
" def __init__(self):\n", | |
" self.data = \"spam\"\n", | |
" \n", | |
" @property\n", | |
" def read_only(self):\n", | |
" return self.data\n", | |
"\n", | |
"exemplo2(MyShyPrivate)\n", | |
"\n", | |
"\n", | |
"class MyShyPublic(metaclass=MetaPrivate):\n", | |
" __public__ = ['read_only']\n", | |
"\n", | |
" def __init__(self):\n", | |
" self.data = \"spam\"\n", | |
" \n", | |
" @property\n", | |
" def read_only(self):\n", | |
" return self.data\n", | |
"\n", | |
"exemplo2(MyShyPublic)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "complimentary-bachelor", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"MyShyPrivate\n", | |
"spam\n", | |
"eggs\n", | |
"Falhou ao tentar acessar o atributo interno \"data\"\n", | |
"MyShyPublic\n", | |
"spam\n", | |
"eggs\n", | |
"Falhou ao tentar acessar o atributo interno \"data\"\n" | |
] | |
} | |
], | |
"source": [ | |
"def exemplo3(cls):\n", | |
" print(cls.__name__)\n", | |
" # Uma instência envergonhada.\n", | |
" my_shy = cls()\n", | |
"\n", | |
" # Podemos olhar diretamente o seu atributo \"read_write\"?\n", | |
" print(my_shy.read_write)\n", | |
"\n", | |
" # Podemos alterar o seu atributo \"read_write\"?\n", | |
" my_shy.read_write = \"eggs\"\n", | |
"\n", | |
" # Ok, mostre o novo valor de \"read_write\".\n", | |
" print(my_shy.read_write)\n", | |
"\n", | |
" # Mas podemos olhar diretamente o seu atributo interno \"data\"?\n", | |
" try:\n", | |
" # Erro de atributo: para código externo, my_shy não tem atributo \"data\".\n", | |
" print(my_shy.data)\n", | |
"\n", | |
" except AttributeError:\n", | |
" print('Falhou ao tentar acessar o atributo interno \"data\"')\n", | |
"\n", | |
"\n", | |
"class MyShyPrivate(metaclass=MetaPrivate):\n", | |
" __private__ = ['data']\n", | |
"\n", | |
" def __init__(self):\n", | |
" self.data = \"spam\"\n", | |
" \n", | |
" @property\n", | |
" def read_write(self):\n", | |
" return self.data\n", | |
" \n", | |
" @read_write.setter\n", | |
" def read_write(self, value):\n", | |
" self.data = value\n", | |
"\n", | |
"exemplo3(MyShyPrivate)\n", | |
"\n", | |
"\n", | |
"class MyShyPublic(metaclass=MetaPrivate):\n", | |
" __public__ = ['read_write']\n", | |
"\n", | |
" def __init__(self):\n", | |
" self.data = \"spam\"\n", | |
" \n", | |
" @property\n", | |
" def read_write(self):\n", | |
" return self.data\n", | |
" \n", | |
" @read_write.setter\n", | |
" def read_write(self, value):\n", | |
" self.data = value\n", | |
"\n", | |
"exemplo3(MyShyPublic)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "infectious-rocket", | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.7.7" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment