Reputation: 1331
I'm trying to implement an (admittedly unPythonic) way of encapsulating a lot of instance variables.
I have these variables' names mapped to the respective values inside a dictionary, so instead of writing a lot of boilerplate (i.e. self.var = val
, like times 50), I'm iterating over the dictionary while calling __setattr__()
, this way:
class MyClass:
__slots__ = ("var1", "var2", "var3")
def __init__(self, data):
for k, v in data.items():
self.__setattr__(k, v)
Then I would override __setattr__()
in a way that controls access to these properties.
From within __setattr__()
, I'd check if the object has the property first, in order to allow setattr calls inside __init__()
:
def __setattr__(self, k, v):
if k in self.__class__.__slots__:
if hasattr(self, k):
return print("Read-only property")
super().__setattr__(k, v)
The problem is, I also need some of these properties to be writeable elsewhere in myClass
, even if they were already initialized in __init__()
. So I'm looking for some way to determine if setattr was called inside the class scope or outside of it, e.g.:
class MyClass:
__slots__ = ("var",)
def __init__(self):
self.__setattr__("var", 0)
def increase_val(self):
self.var += 1 # THIS SHOULD BE ALLOWED
my_obj = MyClass()
my_obj.var += 1 # THIS SHOULD BE FORBIDDEN
My pseudo definition would be like:
# pseudocode
def setattr:
if attribute in slots and scope(setattr) != MyClass:
return print("Read-only property")
super().setattr
Also, I'd rather not store the entire dictionary in one instance variable, as I need properties to be immutable.
Upvotes: 1
Views: 766
Reputation: 1331
Answering my own question to share with anyone with the same issue.
Thanks to @DeepSpace in the comments I've delved a bit into the frame inspection topic which I totally ignored before.
Since the well known inspect
library relies on sys._getframe()
in some parts, namely the parts that I'm mainly interested in, I've decided to use sys
instead.
The function returns the current frame object in the execution stack, which is equipped with some useful properties.
E.g., f_back
allows you to locate the immediate outer frame, which in case __setattr__()
was called within the class, is the class itself.
On the outer frame, f_locals
returns a dictionary with the variables in the frame's local scope and their respective values.
One can look for self
inside f_locals
to determine wether the context is a class, although it's a bit 'dirty' since any non-class context could have a self
variable too.
However, if self
is mapped to an object of type MyClass
, then there shouldn't be ambiguities.
Here's my final definition of __setattr__()
def __setattr__(self, k, v):
if k in self.__class__.__slots__:
self_object = sys._getframe(1).f_back.f_locals.get("self")
if self_object is None or self_object.__class__ != MyClass:
return print(k, "is a read-only property")
super().__setattr__(k, v)
As a conclusion, I feel like pursuing variable privacy in Python is kind of going against the tide; it's definitely a cleaner solution to label variables as 'protected' according to the recognized standard, without bothering too much about the actual accessibility.
Another side note is that frame inspection doesn't look like a very reliable approach for applications meant for production, but more like a debugging tool. As a matter of fact, some inspect
functions do not work with some Python implementations, e.g. those lacking stack frame support.
Upvotes: 2