在继承自int或float或str

时间:2015-12-25 23:39:58

标签: python class inheritance python-internals

当我尝试设置继承自str的类的参数值时,会引发错误。仅当我尝试使用myClass(arg = 'test')访问参数时才会发生错误。错误是:

TypeError: 'arg' is an invalid keyword argument for this function

问题显示在这个例子中:

class A(str):    
    def __init__(self, arg):
        pass

A("test") # Works well
A(arg = "test") # Fails

只有最后一行才会引发错误。前一行效果很好。 对于从intfloat继承的类,问题是相同的。

更新(解决方案):

我找到了这些链接的解决方案:

解决方案是:

class A(str):

    def __new__(cls, *args, **kwargs):
        return str.__new__(cls)

    def __init__(self, arg01):
        print(arg01)

A(arg01= "test")

我不确切知道为什么会有效,我会对此进行调查。如果有人有明确的解释,我很感兴趣,我提前感谢他。

更新(我的解释):

我不确定我会说什么,但这就是我所理解的。

想象一下没有任何遗产的班级'myClass'。 当我这样做myInstance = myClass()时,会发生以下情况:

执行方法myClass.__new__。此方法将创建对象myInstance__new__是真正的构造函数(__init__不是构造函数!)。在伪代码中,__new__看起来像这样:

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.

当我们使用不可变类型(str,int,float,tuple)时,情况会有所不同。在之前的伪代码中,我写了def __new__(cls, *args, **kwargs)。对于不可变类型,方法__new__的伪代码更像是def __new__(cls, anUniqueValue)。我并不真正理解为什么immutableTypes.__new__的行为与其他行为不同,但事实并非如此。你可以在这个例子中看到它:

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).

从那里,我们可以理解为什么之前提出的解决方案有效。我们所做的是编辑不可变类型的方法__new__,就像一个可变类型一样。

def __new__(cls, *args, **kwargs):
    return str.__new__(cls)

这两行将def __new__ (cls, anUniqueValue)转换为def __new__ (cls, *args, **kwargs)

我希望我的解释几乎清楚,没有太多错误。如果您说法语,您可以通过该链接了解更多信息:http://sametmax.com/la-difference-entre-new-et-init-en-python/

1 个答案:

答案 0 :(得分:2)

你写的内容基本上是正确的,这里和那里都有一些错误。

class A(str):

    def __new__(cls, *args, **kwargs):
        return str.__new__(cls)

    def __init__(self, arg01):
        print(arg01)

这不完全正确:如果你没有将任何参数传递给str.__new__,你的新实例将相当于一个空字符串。

class A(str):

    def __new__(cls, arg):
        return str.__new__(cls, arg)

    def __init__(self, arg):
        print(arg)

通常,__new____init__应具有相同的签名。这不是必需的,但在这种情况下,您需要将arg传递给str.__new__,因此您必须拦截arg

  

执行方法myClass.__new__。此方法将创建对象myInstance__new__是真正的构造函数(__init__不是构造函数!)。在伪代码中,__new__看起来像这样:

__new__无需致电__init__,正如此简单示例所示:

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__

正如您所看到的,在我的__new__中,我没有明确地致电__init__object.__new__也没有打电话给__init__

只要__init__返回该类型的实例,Python解释器就会自动调用

__new__

  

当我们使用不可变类型(str,int,float,tuple)时,情况会有所不同。

这不完全正确。当我们从未使用默认__new__实现的类型继承时,情况不同

默认__new__实现(即object.__new__)非常宽松,并忽略每个位置参数和关键字参数。如果用一个不太宽松的实现替换它,那么就会出现像你正在观察的那样的问题。

重要的是要理解问题不是由非默认__new__本身引起的,而是由于我们的__init____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).

你明白了。只有一点是错的:str.__new__的正确签名是:

def __new__(cls[, object[, encoding[, errors]]])

cls之外的所有参数都是位置和关键字参数。事实上你可以这样做:

>>> class C(str):
...     def __init__(self, object):
...         pass
... 
>>> C('abc')
'abc'
>>> C(object='abc')
'abc'
你知道吗?我们使用与__init__兼容的str.__new__签名,现在我们可以避免覆盖__new__