classification
Title: Misleading error from type() when passing unknown keyword argument
Type: behavior Stage: needs patch
Components: Documentation, Library (Lib) Versions: Python 3.4, Python 3.3
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: Ramchandra Apte, cjw296, daniel.urban, docs@python, eric.araujo, ncoghlan, terry.reedy
Priority: normal Keywords: easy

Created on 2013-02-11 08:26 by cjw296, last changed 2013-02-18 09:53 by ncoghlan.

Messages (9)
msg181884 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2013-02-11 08:26
>>> from types import new_class
>>> from datetime import datetime
>>> new_class('tdatetime', (datetime, ), kwds={'foo':'bar'})
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/src/Python-3.3.0/Lib/types.py", line 52, in new_class
    return meta(name, bases, ns, **kwds)
TypeError: type() takes 1 or 3 arguments

I'm guessing ns and kwds should be combined before being passed through to meta? (meta is 'type' in this case)
msg182064 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2013-02-13 23:16
Eric, surely this is a bugfix candidate for 3.3.1?
msg182065 - (view) Author: √Čric Araujo (eric.araujo) * (Python committer) Date: 2013-02-13 23:17
> I'm guessing ns and kwds should be combined before being passed through to meta?
Possibly; can you try that?

> surely this is a bugfix candidate for 3.3.1?
If we get a patch with a test in time, otherwise 3.3.2.
msg182118 - (view) Author: Daniel Urban (daniel.urban) * Date: 2013-02-14 19:26
I don't think this is a bug:

>>> from datetime import datetime
>>> class tdatetime(datetime, foo='bar'):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments
>>>
msg182142 - (view) Author: Ramchandra Apte (Ramchandra Apte) * Date: 2013-02-15 13:23
@Daniel Urban
Me too.
msg182175 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-02-15 21:09
As far as I know, currently, the only valid 'keyword' argument for a class statement is 'metaclass' and that is so advanced that it is not mentioned in *8.7. Class definitions* but only in the linked section *3.3.3. Customizing class creation*. The types.newclass doc also only mentions 'metaclass' as a possible keyword. (Maybe it should currently say that that is the only possibility, but perhaps the window was being left open for possible additions in the future.) So I agree that passing anything else is a bug and should raise. If so, this issue should be closed.
msg182211 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-02-16 05:05
The types.new_class docs are quite clear that the supplied keyword arguments are equivalent to those provided in the type header (if you want to pre-populate the namespace, that's what exec_body is for). The problem here is that the dual signature of type (retrieving the type of an existing object, or creating a new one), and the fact that type.__prepare__ ignores all arguments, means the error message is thoroughly misleading when you pass an unknown keyword argument:

>>> type.__prepare__(foo=1)
{}
>>> type("Example", (), {}, foo=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments

>>> class Example(foo=1): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments

>>> import types
>>> types.new_class("Example", (), dict(foo=1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/ncoghlan/devel/py3k/Lib/types.py", line 52, in new_class
    return meta(name, bases, ns, **kwds)
TypeError: type() takes 1 or 3 arguments

The reason type.__prepare__ ignores its arguments is to make it easy for people to use type to anchor a custom metaclass hierarchy and call super().__prepare__(name, bases, **kwds) without needing to worry much about filtering the keyword arguments. (The class machinery intercepts the metaclass hint and never passes it to __prepare__ or the metaclass constructor).

That means the real change needed here is to update type's error message for bad arguments to properly report unknown keyword errors when using the PEP 3115 metaclass API.
msg182304 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2013-02-18 08:14
Some background: I hit this problem when adding Python 3 compatibility 
to one of my libraries, where I had the following code:

from types import ClassType
...
class_ = ClassType(n, (sometype, ), dict(class_attr1='foo', 
class_attr2='bar')

It wasn't at all clear how to port this to Python 3, given that 
ClassType was gone.

types.new_class looks fair game, but the help is not exactly helpful:

new_class(name, bases=(), kwds=None, exec_body=None)
     Create a class object dynamically using the appropriate metaclass.

No indication there as to what type should be passed for kwds or exec_body.

I guessed and, by the sound of it, guessed wrong.
I'd certainly agree that the error message is very misleading.

cheers,

Chris
msg182310 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-02-18 09:53
For the simple case where you don't need to provide a metaclass hint, the simplest conversion is actually directly to the 3-argument form of type().

As far as the docstring goes, I don't want to make it as long as the full docs, but it could probably stand to be longer than it is.
History
Date User Action Args
2013-02-18 09:53:40ncoghlansetmessages: + msg182310
2013-02-18 08:14:21cjw296setmessages: + msg182304
2013-02-16 05:05:12ncoghlansettitle: Incorrect use of type function in types.new_class -> Misleading error from type() when passing unknown keyword argument
nosy: + docs@python

messages: + msg182211

assignee: docs@python
components: + Documentation
2013-02-15 21:09:42terry.reedysetnosy: + terry.reedy
messages: + msg182175
2013-02-15 13:23:58Ramchandra Aptesettype: behavior
2013-02-15 13:23:48Ramchandra Aptesetnosy: + Ramchandra Apte
messages: + msg182142
2013-02-14 19:26:33daniel.urbansetnosy: + daniel.urban
messages: + msg182118
2013-02-14 15:18:29pitrousetnosy: + ncoghlan
2013-02-13 23:17:42eric.araujosetmessages: + msg182065
2013-02-13 23:16:42cjw296setmessages: + msg182064
2013-02-13 23:15:38eric.araujosettitle: TypeError: type() takes 1 or 3 arguments -> Incorrect use of type function in types.new_class
nosy: + eric.araujo

versions: + Python 3.4
keywords: + easy
stage: needs patch
2013-02-11 08:26:41cjw296create