An error is raised when I try to set the value of an argument of a class who inherits from str
. The error occurs only when I try to access the argument with myClass(arg = 'test')
. The error is :
TypeError: 'arg' is an invalid keyword argument for this function
The issue is shown in this exemple :
class A(str):
def __init__(self, arg):
pass
A("test") # Works well
A(arg = "test") # Fails
Only the last line raises the error. The previous line works well.
The problem is the same with classes that inherit from int
or float
.
UPDATE (solution) :
I found a solution with these links :
The solution is :
class A(str):
def __new__(cls, *args, **kwargs):
return str.__new__(cls)
def __init__(self, arg01):
print(arg01)
A(arg01= "test")
I don't know exactly why that works and I will investigate on it. If someone has got a clear explanation, I'm interested and I thank him in advance.
UPDATE (my explanation) :
I'm not sure at all of what I will say but that is what I understood.
Imagine a class 'myClass'
without any heritance.
When I do this myInstance = myClass()
, here is what happens :
The method myClass.__new__
is executed. This method will create the object myInstance
. __new__
is the real constructor (__init__
isn't a constructor !). In pseudocode, __new__
look something like this :
def __new__ (cls, *args, **kwargs):
myInstance = # Do some stuff to create myInstance, an object of the type passed as argument (cls).
# Add the method __init__ to myInstance.
# Call __init__ and pass to it the list 'args' and the dictionary 'kwargs' (both come from arguments of __new__). Pass to it also the object itself (self) :
obj.__init__(self, args, kwargs) :
# Do some stuff.
The situation is a little different when we are using immutable types (str, int, float, tuple). In the previous pseudocode, I wrote def __new__(cls, *args, **kwargs)
. With immutable types, the pseudocode for the method __new__
is more like this def __new__(cls, anUniqueValue)
. I don't realy understand why the behavior of immutableTypes.__new__
is deferent of others but it's the case. You can see it in this exemple :
class foo():
def __init__(self):
pass
foo.__new__(foo, arg = 1)
# That works because the method __new__ look like this : def __new__(*args, **kargs).
str.__new__(str, arg = 1)
# That fails because we are trying to pass the attribute 'arg' to a method which look like this : def __new__(anUniqueValue).
From there, we can understand why the solution presented earlier works. What we do is editing the method __new__
of an immutable type to works just like a mutable type.
def __new__(cls, *args, **kwargs):
return str.__new__(cls)
These 2 lines convert def __new__ (cls, anUniqueValue)
to def __new__ (cls, *args, **kwargs)
I hope my explanation is almost clear and without so much mistakes. If you speak french you can learn more on that link : http://sametmax.com/la-difference-entre-new-et-init-en-python/
What you wrote is essentially right, there are a few mistakes here and there.
class A(str):
def __new__(cls, *args, **kwargs):
return str.__new__(cls)
def __init__(self, arg01):
print(arg01)
This is not completely correct: if you don't pass any argument to str.__new__
, your new instance will be the equivalent of an empty string.
class A(str):
def __new__(cls, arg):
return str.__new__(cls, arg)
def __init__(self, arg):
print(arg)
In general, __new__
and __init__
should have the same signature. It is not a requirement, but in this case you need to pass arg
to str.__new__
so you must intercept arg
.
The method
myClass.__new__
is executed. This method will create the objectmyInstance
.__new__
is the real constructor (__init__
isn't a constructor !). In pseudocode,__new__
look something like this :
It is not the responsibility of __new__
to call __init__
, as demonstrated by this simple example:
class C:
def __new__(cls):
print('entering __new__')
self = super().__new__(cls)
print('exiting __new__')
return self
def __init__(self):
print('entering __init__')
super().__init__()
print('exiting __init__')
C()
# Output:
# entering __new__
# exiting __new__
# entering __init__
# exiting __init__
As you can see, in my __new__
I didn't call __init__
explicitly, and object.__new__
is not calling __init__
either.
__init__
is automatically called by the Python interpreter itself whenever __new__
returns an instance of the type.
The situation is a little different when we are using immutable types (str, int, float, tuple).
This is not entirely true. The situation is different when we are inheriting from a type that is not using the default __new__
implementation.
The default __new__
implementation (i.e. object.__new__
) is very permissive and ignores every positional argument and keyword argument. If you replace it with a less permissive implementation, then problems like the one you are observing happen.
What is important to understand is that the problem is not brought by the non-default __new__
itself, but by the fact that our __init__
is not compatible with __new__
.
foo.__new__(foo, arg = 1) # That works because the method __new__ look like this : def __new__(*args, **kargs). str.__new__(str, arg = 1) # That fails because we are trying to pass the attribute 'arg' to a method which look like this : def __new__(anUniqueValue).
You got it. Just one bit is wrong: the correct signature for str.__new__
is:
def __new__(cls[, object[, encoding[, errors]]])
All arguments except cls
are both positional and keyword argument. In fact you can do:
>>> class C(str):
... def __init__(self, object):
... pass
...
>>> C('abc')
'abc'
>>> C(object='abc')
'abc'
You see? We used a signature for __init__
that is compatible with str.__new__
and now we can avoid overriding __new__
!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With