This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Allow setting __classcell__
Type: Stage:
Components: Interpreter Core Versions: Python 3.11
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: douglas-raillard-arm, vstinner
Priority: normal Keywords:

Created on 2022-03-28 14:32 by douglas-raillard-arm, last changed 2022-04-11 14:59 by admin.

Messages (4)
msg416172 - (view) Author: Douglas Raillard (douglas-raillard-arm) Date: 2022-03-28 14:32
The cell object __classcell__ currently cannot be set by code invoked by metaclass.__new__. Attempts to do so will be caught by builtin___build_class__ in bltimodule.c:

                } else {
                    const char *msg =
                        "__class__ set to %.200R defining %.200R as %.200R";
                    PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls);
                }

This is unfortunate as there is a use-case for such trickery: if the method of a class A are only used to be grafted onto another class B (monkey patching), A.__classcell__ should be set to B so that super() works as expected.

This can be useful in contexts where e.g. A.__new__ returns instances of B. B is not necessarily something under direct user control (it can be dynamically created by inheriting from a "template"), so A would be a better place for users to define custom method.
msg416173 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2022-03-28 15:15
> This is unfortunate as there is a use-case for such trickery: if the method of a class A are only used to be grafted onto another class B (monkey patching), A.__classcell__ should be set to B so that super() works as expected.

Would you elaborate your use case?

Would bpo-47143 "Add functools.copy_class() which updates closures" solve your use case?
msg416187 - (view) Author: Douglas Raillard (douglas-raillard-arm) Date: 2022-03-28 16:27
> Would bpo-47143 "Add functools.copy_class() which updates closures" solve your use case?

This looks like a similar issue indeed. If I'm able to copy a class "cleanly" (as defined in this other thread), that may solve the problem (hard to tell precisely until trying it though, the devil is in the details).

> Would you elaborate your use case?

As you probably expect, it's a relatively tricky metaprogramming bit of code. It's implementing something akin to higher kinded types, similar in spirit to the following example where you can have e.g. containers that "know" the item type they contain:

	class ContainerBase:
		ITEM = None

		@classmethod
		def apply(cls, typ):
			...


		@classmethod
		def empty_list(cls):
			...
                  
	class List(ContainerBase):
		pass
	
	class ListOfInt(List.apply(int)):
		pass

The goal is to allow "ListOfInt" to define custom methods if we want in its body.

"ListOfInt" cannot return instances of itself [1] so I'm trying to "cherry pick" its method and graft them onto the type that is actually instantiated.

[1] We e.g. want to create an empty list of the right type. "List" already provides the method for such factory, but it will not return an actual instance of ListOfInt, since it does not even know this class exists. What it will use is an internal type that it created using the "ITEM" class attribute in e.g. List.__init_subclass__, and that is what will get instantiated. This "internal class" could still be customized by ListOfInt to have some custom methods though, which is what I'm trying to achieve.
msg416188 - (view) Author: Douglas Raillard (douglas-raillard-arm) Date: 2022-03-28 16:28
EDIT: empty_list() is a class method of List, not ContainerBase
History
Date User Action Args
2022-04-11 14:59:57adminsetgithub: 91300
2022-03-28 16:28:10douglas-raillard-armsetmessages: + msg416188
2022-03-28 16:27:02douglas-raillard-armsetmessages: + msg416187
2022-03-28 15:15:12vstinnersetnosy: + vstinner
messages: + msg416173
2022-03-28 14:32:05douglas-raillard-armcreate