This proposal is the abridgment of private_attribute_cpp:
Abstract
Python now use name-magging on the name with pre-double underline to create the namespace for this class. However, when two classes have same name (or the difference is just the number of prefer underline), if they appear in one inheritance chain, there will be still conflict. You can use these codes to reqppear this problem (this example has been changed so some comments may seems to be strange):
One module export PublicClass and public_method in its document and pyi file:
class PublicClass:
def __init__(self): ...
def start(self) -> None: ...
def public_function(obj: PublicClass): ...
The document says that the user should inherit the PublicClass and define its custom start method. Finally the public_function will call it.
For simple maintenance, you use the Core and MainClass to split different logic in different way:
from module import PublicClass, public_function
class Core(PublicClass):
def __init__(self):
super().__init__()
self.__state = 0
def start(self):
super().start()
self.__state += 1
class MainClass(Core):
# some other codes
...
a = MainClass()
public_function(a)
However, you get:
Traceback (most recent call last):
File "C:\Users\hh180\OneDrive\Desktop\fake_projects\conflict_by_private_class\script.py", line 18, in <module>
public_function(a)
File "C:\Users\hh180\OneDrive\Desktop\fake_projects\conflict_by_private_class\module.py", line 19, in public_function
obj.start()
File "C:\Users\hh180\OneDrive\Desktop\fake_projects\conflict_by_private_class\script.py", line 10, in start
self.__state += 1
TypeError: can only concatenate str (not "int") to str
The reason is that the author also did this split:
class _Core:
def __init__(self):
self.__state = "uninit"
def start(self):
self.__state = "init"
class _BaseClass(_Core):
# some code
...
class PublicClass(_BaseClass):
# some code
...
def public_function(obj: PublicClass):
obj.start()
The conflict is: under the class _Core and Core, the name magging for one attribute name still gets same name.
Proposal
This proposal use __private_attributes__ declaration with type-level namespace to fix it.
All heaptypes have the attribute __private_attributes_dict__ to store the private attribute for different instances.
Usage
In compile time, the interpreter will bind the class-level codes to this class. For example:
class Example:
class InExample:
def some_function(self): ... # This code will be binded to only "InExample"
def __init__(self): ... # This code will be binded to "Example"
def some_function(self): # This code will be binded to "Example"
a = lambda: ... # This code will be binded to "Example"
def b(): # This code will be binded to "Example"
def in_b(): ... # This code will be binded to "Example"
class C:
def d(self): ... # This code will be binded only to "C"
e = (i for i in something) # This code will be binded to "Example"
When define the class, you can define “__private_attributes__” to declare which attributes will be stored to private dict:
class MyClass:
__private_attributes__ = ("_secret",)
def __init__(self, secret):
self._secret = secret
@property
def secret(self):
return self._secret
Now you can get:
>>> a = MyClass(1)
>>> a._secret = 2
>>> a._secret
2
>>> a.secret
1
Outside of the class it will visit __dict__ to store or load. But in the code of the class it will visit cls.__private_attributes_dict__[id(self)][name].
Subclasses will inherit this attribute. If subclasses define this too, they will be merged:
class SubClass(MyClass):
__private_attributes__ = ("_other_secret",)
def __init__(self, secret1, secret2, secret3):
super().__init__(secret1)
self._secret = secret2
self._other_secret = secret3
def get_both_secret(self):
return self.secret, self._secret, self._other_secret
Then you will get:
>>> b = SubClass(1, 2, 3)
>>> b.secret
1
>>> b.get_both_secret()
(1, 2, 3)
You can use SubClass.__private_attributes_dict__[id(b)][name] to change the private attribute of b outside of the SubClass and MyClass.__private_attributes_dict__[id(b)][name] to change the private attribute of b outside of the MyClass.
Compare
To be compared with the name magging, this way will be safer because the namespace isolation is related on the class itself instead of the name.
Notes
- Defining the name in
__private_attribute_dict__that stards and ends with “__” may cause the problems. Don’t do that. - The custom
__getattribute__,__getattr__,__setattr__,__delattr__will not work if the name for the code of this frame is the private attribute of the class. This will protect the parent class. - When the object is collected, all classes on this object type’s inheritance chain will remove the key of this object’s id.