5

I am subclassing a type defined in a C module to alias some attributes and methods so that my script works in different contexts.

How is it that to get this to work, I have to tweak the dictionary of my class manually ? If I don't add a reference to DistanceTo in the dictionnary, I get Point3d has no attribute named DistanceTo.

class Point3d(App.Base.Vector):
      def __new__(cls, x, y, z):
          obj = super(Point3d, cls).__new__(cls)
          obj.x, obj.y, obj.z = x, y, z
          obj.__dict__.update({
               'X':property(lambda self: self.x),
               'Y':property(lambda self: self.y),
               'Z':property(lambda self: self.z),
               'DistanceTo':lambda self, p: self.distanceToPoint(p)})
          return obj
      def DistanceTo(self, p): return self.distanceToPoint(p)

I was thinking that once __new__ had returned an instance I could still populate it with methods and attributes. Can anyone shed some light on this ?

EDIT : The module I import from is FreeCAD. The C base type is defined there. Then Vector is derived form this definition here

EDIT2 : I also tried the following :

class Point3d(App.Base.Vector):
      def __new__(cls, x, y, z):
          obj = super(Point3d, cls).__new__(cls)
          obj.x, obj.y, obj.z = x, y, z
          obj.__dict__.update({
               'X': x, 'Y': y, 'Z': z,
               'DistanceTo':lambda self, p: self.distanceToPoint(p)})
           return obj
       def DistanceTo(self, p): return self.distanceToPoint(p)

and after creating a second point, both Point3d p returns the value of the last point for p.X, p.Y and p.Z no matter what x,y,z parameters were passed at the creation of the instance. p.x, p.y, p.z return the expected values. It seems to indicate that the dictionary is shared between instances.

EDIT 3 : Problem solved ! The Py_TPFLAGS_BASETYPE bit is set to zero to prevent subclassing as explained in the answer below.

4

2 回答 2

2

I don't understand why you want to add the properties dynamically. Just use:

class Point3d(App.Base.Vector):
    def __init__(self, x, y, z):
        super().__init__(x, y, z)  # or maybe  super().__init__([x, y, z])

    @property
    def X(self):
        return self[0]  # guessing that App.Base.Vector works like a list

    @property.setter
    def X(self, value):
        self[0] = value

    # Y and Z likewise.
于 2016-04-19T19:44:34.970 回答
1

I found the answer in PyObjectBase.cpp :

/** \brief 
 * To prevent subclasses of PyTypeObject to be subclassed in Python we should remove 
 * the Py_TPFLAGS_BASETYPE flag. For example, the classes App::VectorPy  and App::MatrixPy
 * have removed this flag and its Python proxies App.Vector and App.Matrix cannot be subclassed.
 * In case we want to allow to derive from subclasses of PyTypeObject in Python
 * we must either reimplment tp_new, tp_dealloc, tp_getattr, tp_setattr, tp_repr or set them to
 * 0 and define tp_base as 0.
 */

This is due to the App::VectorPy class not being implemented to safely support subclassing and therefore the Py_TPFLAGS_BASETYPE bit is set to zero to prevent it from happening.

For information, it is a similar situation to the bytes built-in type that cannot be subclassed. See this discussion to hear from Guido van Rossum why bytes is not subclassable.

于 2016-04-21T00:22:34.767 回答