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 ?
In order to set metaclass of a class, we use the __metaclass__ attribute. Metaclasses are used at the time the class is defined, so setting it explicitly after the class definition has no effect. The best idea I can think of is to re-define whole class and add the __metaclass__ attribute dynamically somehow.
A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.
To create your own metaclass in Python you really just want to subclass type . A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.
In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses.
Bind the current value of key
to the parameter in your fget
and fset
definitions:
fget = lambda self, k=key: getattr(self, "__%s" % k)
fset = lambda self, value, k=key: setattr(self, "__" + k, value)
This is a classic Python pitfall. When you define
fget = lambda self: getattr(self, "__%s" % key)
the value of key
is determined when fget
is called, not when fget
is defined. Since it is a nonlocal variable, its value is found in the enclosing scope of the __new__
function. By the time fget
gets called, the for-loop
has ended, so the last value of key
is the value found. Python3's dict.item
method returns items in a unpredictable order, so sometimes the last key is, say, __qualname__
, which is why a suprising error is sometimes raised, and sometimes the same wrong value is returned for all attributes with no error.
When you define a function with a parameter with a default value, the default value is bound to the parameter at the time the function is defined.
Thus, the current values of key
get corrected bound to fget
and fset
when you bind default values to k
.
Unlike before, k
is now a local variable. The default value is stored in fget.__defaults__
and fset.__defaults__
.
Another option is to use a closure. You can define this outside of the metaclass:
def make_fget(key):
def fget(self):
return getattr(self, "__%s" % key)
return fget
def make_fset(key):
def fset(self, value):
setattr(self, "__" + key, value)
return fset
and use it inside the metaclass like this:
result_dct[key] = property(make_fget(key), make_fset(key))
Now when fget
or fset
is called, the proper value of key
is found in the enclosing scope of make_fget
or make_fset
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With