Subclassing builtins or another alternative

Jason Orendorff jason at jorendorff.com
Tue Dec 11 21:38:35 EST 2001


> I have an external C-extension type. Let's call it "Agent".
> I would like to allow coders using Agent to use it syntactically
> as a normal baseclass. For example,
> 
> import agent
> 
> class Pilot ( agent.Agent ):
> 	i=3
> 	def f(self):
> 		pass
> 
> Misfortunately, either one can't do this or I simply misunderstand
> what to hook up in the extension. Neither the setattr() or setattro()
> methods appear to get invoked by either the class-level data
> assignment of "i=3" or the definition of the f method on the class.

Warning!  You are entering a forbidden area!  Humankind is not ready
for these secrets!  If you proceed, your brain will melt.

The easy way out:  rename agent.Agent as something like agent._agent
and writing a simple wrapper for it (class agent.Agent) in Python.
Expose and document the wrapper class only.  Users can subclass it
all day, because it's an ordinary Python class from the ground up.

However, if you're committed to making a real C-based extension type
that can be subclassed in Python, read on.  (To avoid brain meltage,
you might want to grab something cold to drink.)


> [...] can I do what I'm trying to do? If so, how?

Yes.  Two ways, in fact.

1.  The exact Python code above works fine in 2.2b2 if you
substitute "str" for "agent.Agent".  Hmm.

So for the 2.2-and-later world, maybe Objects/stringobject.c
has the solution.  (Wild guess: try adding Py_TPFLAGS_BASETYPE
to your type's tp_flags.)


2.  If you want to go for the Big Kahuna and be backwardly
source-compatible to pre-2.2, it's much worse.  :)

When Python sees the "class Pilot" definition above,
what happens is something like this:

  pilot__bases__ = (agent.Agent,)
  pilot__dict__ = {}

  # Run the code to create the members of the new class
  exec "i=3\ndef f(self): pass\n" in globals(), pilot__dict__

  # Now create the class itself from its components.
  base_class = Py_find_best_base_class(pilot__bases__)
  class_factory = type(base_class)
  Pilot = class_factory("Pilot", pilot__bases__, pilot__dict__)

As you see, nothing is ever assigned with setattr().  "i"
and "f" get plugged directly into the __dict__ dictionary.

Also note that type(base_class) must be callable.

I imagine you're using PyObject_HEAD_INIT(&PyType_Type).
So type(agent.Agent) is PyType_Type.

Well, prior to 2.2, PyType_Type was not callable, and there
wasn't any 2.2-style magic to "just make it work anyway".
So if you wanted a magically subclassable extension type,
you had to create your own callable *metaclass*.  It was tough.
(Jim Fulton's ExtensionClasses provides this, I recall.)

Hope this helps!  Good luck!

-- 
Jason Orendorff    http://www.jorendorff.com/





More information about the Python-list mailing list