What is the correct way to implement a data descriptor inside a metaclass? In the following (trivial) example, I wish to always append a question mark to the desired value before setting it:
class AddQDesc:
def __init__ (self, name):
self.name = name
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
# What should go here ?
#setattr(instance, self.name, "{}?".format(value)) <- This gives me recursion error
#instance.__dict__[self.name] = "{}?".format(value) <- This gives me proxymapping error
pass
class Meta (type):
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
First, it looks like the descriptor was not used when I initialized var to 5. Can I somehow apply descriptor protocol here as well? (Make it "5?")
Second, how should the value be updated in the __set__ method? Updating the __dict__ gives me "TypeError: 'mappingproxy' object does not support item assignment" and using setattr gives me "RecursionError: maximum recursion depth exceeded while calling a Python object".
As I commented in the question, this is tricky - because there is no way from Python code to change a class' __dict__ attribute directly - one have to call setattr and let Python set a class attribute - and, setattr will "see" the descriptor in the metaclass, and call its __set__ instead of modifying the value in the class __dict__ itself. So, you get an infinite recursion loop.
Therefore, if you really require that the attribute proxied by the descriptor will "live" with the same name in the class'dict, you have to resort to: when setting the value, temporarily remove the descriptor from the metaclass, call setattr to set the value, and then restoring it back.
Also, if you want the values set in the class body to be handled
through the descriptor, they have to be set with setattr after the
class is created - type.__new__ won't check for the descriptor
as it builds the initial class __dict__.
from threading import Lock
class AddQDesc:
def __init__ (self, name):
self.name = name
self.lock = Lock()
def __get__ (self, instance, owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self, instance, value):
owner_metaclass = type(instance)
with self.lock:
# Temporarily remove the descriptor to avoid recursion problems
try:
# Note that a metaclass-inheritance hierarchy, where
# the descriptor might be placed in a superclass
# of the instance's metaclass, is not handled here.
delattr(owner_metaclass, self.name)
setattr(instance, self.name, value + 1)
finally:
setattr(owner_metaclass, self.name, self)
class Meta (type):
def __new__(mcls, name, bases, namespace):
post_init = {}
for key, value in list(namespace.items()):
if isinstance(getattr(mcls, key, None), AddQDesc):
post_init[key] = value
del namespace[key]
cls = super().__new__(mcls, name, bases, namespace)
for key, value in post_init.items():
setattr(cls, key, value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
If you don't need the value to live in the class' __dict__, I'd suggest just storing it elsewhere - a dictionary in the descriptor instance for example, having the classes as keys, will suffice - and will be far less weird.
class AddQDesc:
def __init__ (self, name):
self.name = name
self.storage = {}
def __get__ (self, instance, owner):
if not instance: return self
return self.storage[instance]
def __set__ (self, instance, value):
self.storage[instance] = value + 1
Related
My class code in edit (For presentation, not a problem for my question.)
class TxtFile:
"""Responsible for the file.
Creates objects that store the text of a '.txt' file
"""
file_count = 0
#staticmethod
def question():
"""Call question()
"""
print("""If you need:
open '.txt' file for read, use .open_file
write a '.txt' file, use .write_file
append text to '.txt' file, use .append_file""")
#classmethod
def kids(cls):
"""Counts the number of open files.
Variable keeper - file_count
"""
print('I have', cls.file_count, 'file')
def __init__(self):
self.__file_name = None
self.__file_path = None
TxtFile.file_count += 1
#property
def name(self):
if self.__file_name is None:
print('You didn\'t enter a file name.\n')
else:
return self.__file_name
#property
def path(self):
if self.__file_path is None:
print('Path not specified.')
fpath.display_catalog(DATA_PATH)
else:
return self.__file_path
#name.setter
def name(self, name):
if self.__file_name is not None:
print('The file already has the specified name.\n')
else:
if self.__file_path is not None:
fpath.search_file(self.__file_path, name)
else:
print('You did not specify the path to the',
'directory with the file.\n')
#path.setter
def path(self, path):
if self.__file_path is not None:
print('The file already has the specified path.')
else:
self.__file_path = path
def open_file(self, method='read'):
"""Responsible for opening a file.
Takes 3 arguments: read, write, append
Equal: rt, wt, at
"""
if method == 'read':
pass
if method == 'write':
pass
if method == 'append':
pass
The code I want to do:
def display_info(cls):
method_list = [method for method in dir(cls)
if callable(getattr(cls, method))
and method.startswith('__') is False]
for method in method_list:
method_doc = cls.method.__doc__
print(method + ':')
if method_doc is None:
print('This method does not have docstring.')
else:
print('This is the docstring',
method_doc)
display_info(TxtFile)
Error thrown: AttributeError: type object 'TxtFile' has no attribute 'method'
I tried in this way:
method_doc = cls.__dict__[method].__doc__
Output:
This is the docstring classmethod(function) -> method
Convert a function to be a class method.
A class method receives the class as implicit first argument,
just like an instance method receives the instance.
To declare a class method, use this idiom:
class C:
#classmethod
def f(cls, arg1, arg2, ...):
...
It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.
If a class method is called for a derived class, the derived class
object is passed as the implied first argument.
Class methods are different than C++ or Java static methods.
If you want those, see the staticmethod builtin.
open_file:
This is the docstring Responsible for opening a file.
Takes 3 arguments: read, write, append
Equal: rt, wt, at
question:
This is the docstring staticmethod(function) -> method
Convert a function to be a static method.
A static method does not receive an implicit first argument.
To declare a static method, use this idiom:
class C:
#staticmethod
def f(arg1, arg2, ...):
...
It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). Both the class and the instance are ignored, and
neither is passed implicitly as the first argument to the method.
Static methods in Python are similar to those found in Java or C++.
For a more advanced concept, see the classmethod builtin.
I want to have a function that will output dcstring of class methods.
I have been learning Python for less than a month, so my knowledge is limited. In the code, I use what I know. I would like to know the solution and get an answer how it works.
Here in this code I am just calling out my static method, but it prints my instance variable too. Could you please explain the reason for that, and how to avoid them being printed?
Like below:
I am a static Method
None
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
print("I am a static Method")
p1 = Player()
print(p1.demo())
As Python docs says:
Print objects to the text stream file, separated by sep and followed
by end. sep, end, file, and flush, if present, must be given as
keyword arguments.
So you can return your message in method and then just print it:
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
return "I am a static Method"
p1 = Player()
print(p1.demo())
I want a callback to get called whenever a certain attribute of object A is changed.
I'm aware that this question is related to Observer Pattern and descriptors in Python. However, it seems descriptors could only detect explicit changes via dot access.
For instance:
class Observer(object):
def __init__(self, callback=None):
self.__callback = callback
def __set_name__(self, owner, name):
self.__name = name
def __set__(self, obj, value):
obj.__dict__[self.__name] = value
self.trigger()
def __get__(self, obj, type=None):
return obj.__dict__.get(self.__name)
def trigger(self):
self.__callback()
def hello():
print('hello')
class MyClass:
data = Observer(hello)
a = MyClass()
a.data = [[1],2,3]
a.data.append(4)
a.data[0][0] = -1
In the above code, the callback is only called once for the initialization of the data. However, I want it to be called 3 times. I'm not tied to using descriptors but I do want the method to work on any data types, such as list, dict and etc.
Below, I show a simplified example of a more complicated code, but nonetheless, it fully represents the issue that I have encountered.
Part 1: this works fine, no issues:
class Animal():
def __init__(self, animal_name = "no name given"):
self.set_name(animal_name)
def get_name(self):
return self._animal_name
def set_name(self, animal_name):
self._animal_name = animal_name
class Dog(Animal):
def __init__(self, dog_breed = "no breed", dog_name = "no name given"):
self._dog_breed = dog_breed
super().__init__(dog_name)
def get_breed(self):
print(self._dog_breed)
x = Dog('Greyhound', 'Rich')
Part 2: after introducing getter & setter decorators, the code stops working:
class Animal():
def __init__(self, animal_name = "no name given"):
#THE LINE BELOW SEEMS TO CAUSE AN ISSUE
self.name(animal_name)
#property
def name(self):
return self._animal_name
#name.setter
def name(self, animal_name):
self._animal_name = animal_name
class Dog(Animal):
def __init__(self, dog_breed = "no breed", dog_name = "no name given"):
self._dog_breed = dog_breed
super().__init__(dog_name)
def get_breed(self):
print(self._dog_breed)
x = Dog('Greyhound', 'Rich')
Output: AttributeError: 'Dog' object has no attribute '_animal_name'
When I keep the decorators in Part 2 but change the constructor in the Animal class to:
class Animal():
def __init__(self, animal_name = "no name given"):
self._animal_name=animal_name
It works.
I am just curious why it doesn't work in the example above in Part 2?
Short answer:
The line
self.name(animal_name)
can be split in two parts:
tmp = self.name
tmp(animal_name)
First, self.name calls the getter and the result is treated as a function. The getter uses return self._animal_name and since the setter has never been called, the respective error occurs.
Long answer:
Let's take the following class:
class Animal:
def __init__(self, animal_name):
self.name(animal_name)
#property
def name(self):
return self._animal_name
#name.setter
def name(self, animal_name):
self._animal_name = animal_name
To understand what the line
self.name(animal_name)
actually does, you first need to understand decorators.
The code
#dec
def func(a, b, ...):
[...]
is equivalent to
def func_impl(a, b, ...):
[...]
func = dec(func_impl)
(except that you can not call func_impl directly). See, for example, PEP 318 for more information.
This means that you can write the Animal class from above without using decorators:
class Animal:
def __init__(self, animal_name):
self.name(animal_name)
def get_name(self):
return self._animal_name
name = property(get_name)
def set_name(self, animal_name):
self._animal_name = animal_name
name = name.setter(set_name)
In order to understand this code, you need to understand the builtin property, which is a class. See the python docs for detailed information.
The line name = property(get_name) creates an object of type property. When retrieving the value of the property, get_name is called.
The line name = name.setter(set_name) first calls name.setter(set_name), which creates a copy of the property, and then overwrites name with this copy. When assigning a value to the copy, set_name is called.
All in all, name is an object of type property that uses get_name as getter and set_name as setter.
How does this help?
You need to understand this: name is not a function. It is a property. It is not callable.
The problematic line
self.name(animal_name)
is actually equivalent to
self.get_name()(animal_name)
which this explains the error message: The constructor calls the getter, which tries to use return self._animal_name. But since the setter has not been called, yet, self._animal_name has not been set.
I am trying to assign dictionary keys to object functions but for some reason it won't work inside of decorators. When I try to call a.run(), self doesn't seem to be passed into the dictionary func. I also don't have access to f.self in decorator so I know it has to be something wrong in there. I have written a simple example of my code. I want it to be something similar to app.route in flask being that it init the mapping between endpoints and functions.
ERROR:
Traceback (most recent call last):
File "main.py", line 27, in <module>
a.run()
File "main.py", line 14, in run
self.rmap[k](data)
TypeError: one_way() missing 1 required positional argument: 'data'
CODE:
class A (object):
def __init__(self):
self.rmap = {}
def route(self, r):
def decorator(f):
self.rmap[r] = f
return f
return decorator
def run(self):
data = [1,2,3]
for k in self.rmap.keys():
self.rmap[k](data)
a = A()
class B (object):
def __init__(self):
pass
#a.route('/one/way')
def one_way (self, data):
print('A WAY:{}'.format(self))
b = B()
a.run()
At the time it's being decorated, one_way() is a plain function, not a method - it only becomes a method when looked up on a B instance. IOW, you need to explicitely provide a B instance when calling it from A().run() (the fact you have a global b instance in your code is irrelevant - the function object stored in a.rmap knows absolutely nothing about it, nor even about the B class FWIW.
To make a long story short, your current design cannot work as is. If you only ever intend to decorate methods (well, functions) from one single class and call them on one single instance of this class, you could pass an instance of this class to a.run() ie:
class A():
# ...
def run(self, obj):
data = [1,2,3]
for k in self.rmap.keys():
self.rmap[k](obj, data)
b = B()
a.run(b)
but this would be of very limited use.
Or you could just use the decorator to "mark" functions to be used for routing (together with the effective route), add some register() methdo to A and explicitely pass B or whatever else instance to this method ie
def route(r):
def decorator(f):
f._A_route = r
return f
return decorator
class A (object):
def __init__(self):
self.rmap = {}
def register(self, *objects):
for obj in objects:
self._register(obj)
def _register(self, obj):
for name in dir(obj):
if name.startswith("_"):
continue
attr = getattr(obj, name)
if callable(attr) and hasattr(attr, "_A_route"):
self.rmap[attr._A_route] = attr
def run(self):
data = [1,2,3]
for k in self.rmap.keys():
self.rmap[k](data)
class B (object):
def __init__(self):
pass
#route('/one/way')
def one_way (self, data):
print('A WAY:{}'.format(self))
if __name__ == "__main__":
a = A()
b = B()
a.register(b)
a.run()
Now there might be better solutions for your concrete use case, but it's impossible to tell without knowing about the whole context etc.
When calling self.rmap[k](data) you are not passing in the self parameter. This has to be an instance of class B in order to work.
Normally you'd just pass on the parameters with which the decorated function was called, but you seem to want to use your decorated function differently. In your case what would work is:
def run(self):
data = [1,2,3]
b = B()
for k in self.rmap.keys():
self.rmap[k](b, data)
You could of course also instantiate the B instance somewhere else if you want to reuse it between calls.