'How to add properties to a metaclass instance?
I would like to define metaclass that will enable me to create properties (i.e. setter, getter) in new class on the base of the class's attributes.
For example, I would like to define the class:
class Person(metaclass=MetaReadOnly):
name = "Ketty"
age = 22
def __str__(self):
return ("Name: " + str(self.name) + "; age: "
+ str(self.age))
But I would like to get something like this:
class Person():
__name = "Ketty"
__age = 22
@property
def name(self):
return self.__name;
@name.setter
def name(self, value):
raise RuntimeError("Read only")
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
raise RuntimeError("Read only")
def __str__(self):
return ("Name: " + str(self.name) + "; age: "
+ str(self.age))
Here is the metaclass I have written:
class MetaReadOnly(type):
def __new__(cls, clsname, bases, dct):
result_dct = {}
for key, value in dct.items():
if not key.startswith("__"):
result_dct["__" + key] = value
fget = lambda self: getattr(self, "__%s" % key)
fset = lambda self, value: setattr(self, "__"
+ key, value)
result_dct[key] = property(fget, fset)
else:
result_dct[key] = value
inst = super(MetaReadOnly, cls).__new__(cls, clsname,
bases, result_dct)
return inst
def raiseerror(self, attribute):
raise RuntimeError("%s is read only." % attribute)
However it dosen't work properly.
client = Person()
print(client)
Sometimes I get:
Name: Ketty; age: Ketty
sometimes:
Name: 22; age: 22
or even an error:
Traceback (most recent call last):
File "F:\Projects\TestP\src\main.py", line 38, in <module>
print(client)
File "F:\Projects\TestP\src\main.py", line 34, in __str__
return ("Name: " + str(self.name) + "; age: " + str(self.age))
File "F:\Projects\TestP\src\main.py", line 13, in <lambda>
fget = lambda self: getattr(self, "__%s" % key)
AttributeError: 'Person' object has no attribute '____qualname__'
I have found example how it can be done other way Python classes: Dynamic properties, but I would like to do it with metaclass. Do you have any idea how this can be done or is it possible at all ?
Solution 1:[1]
@unutbu's answer is perfectly fine and addressed the main caveats very well.
However, there is one more thing to note (I cannot comment yet so here I am posing an entire answer), note that classproperty's and instanceproperty's have different keys.
What you are doing here is entirely based on class properties. If one wishes to make a setter for an "instance" property the key must be f"_{clsname}__{key}". I lack the technical understanding of Python data model to explain why or if what I'm saying is entirely correct or has other caveats. Just posting this here as a side note.
This is especially important if you wish to allow the user to override the methods with custom ones like the example I provide here (I'm not sure even if it has no syntax error but still I post it here):
class MyMeta(type):
def __new__(cls, clsname, bases, info, **kwargs):
property_key = "__my_private" # should have been f"_{clsname}__my_private"
info[property_key] = "default_value"
if "my_getter" not in info:
info["my_getter"] = lambda self, key=property_key: getattr(self, key)
return super().__new__(cls, clsname, bases, info, **kwargs)
class MyClass(metaclass=MyMeta):
def my_getter(self):
return f"will fail to get {self.__my_private}"
MyClass().my_getter() # => Raises: AttributeError "_MyClass__my_private" not found.
# This would work so fine if no `self` keyword was being used (treated as classproperty)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
