【问题标题】:Set the value of an argument in a class who inherits from int or float or str在继承自 int 或 float 或 str 的类中设置参数的值
【发布时间】:2016-03-31 18:11:28
【问题描述】:

当我尝试设置继承自 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/

【问题讨论】:

  • 可能与 stackoverflow.com/questions/3748635/… 有关,尽管我不确定 Alex Martelli 的“同样任意的超类”究竟是什么意思。
  • @Morgan,解释在链接的问题中,也可能有一个可以为你提供清晰的解释;)
  • @Morgan 当您发现到底发生了什么时,会毫不犹豫地回答您自己的问题。我自己并没有完全理解链接问题中的解释。
  • @Morgan,不费吹灰之力,答案中已经很好地解释了,所以如果你有更简单的解释,你一定要把它写成答案

标签: python class inheritance python-internals


【解决方案1】:

你写的基本上是对的,这里和那里都有一些错误。


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__ 看起来像这样:

调用__init__ 不是__new__ 的责任,如下简单示例所示:

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__

每当__new__ 返回该类型的实例时,Python 解释器本身就会自动调用__init__


当我们使用不可变类型(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__

【讨论】:

    猜你喜欢
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    相关资源
    最近更新 更多