随机窃取对子初始化程序的调用

Car*_*ate 5 python subclass

有一种涉及子分类的情况我无法弄清楚。

我正在进行子分类Random(原因超出了重点)。这是我所拥有的一个基本示例:

import random

class MyRandom(random.Random):
    def __init__(self, x):  # x isn't used here, but it's necessary to show the problem.
        print("Before")
        super().__init__()  # Nothing passed to parent
        print("After")

MyRandom([])
Run Code Online (Sandbox Code Playgroud)

上面的代码在运行时给出以下错误(并且不打印“Before”):

>>> import test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\_\PycharmProjects\first\test.py", line 11, in <module>
    MyRandom([])
TypeError: unhashable type: 'list'
Run Code Online (Sandbox Code Playgroud)

对我来说,这没有任何意义。不知何故,即使我没有传递它,参数MyRandom显然也被直接传递给Random.__init__,并且该列表被视为种子。"Before"从不打印,所以显然我的初始化器甚至从未被调用。

我认为这可能是由于Random在 C 中实现的父级造成的,这导致了奇怪的情况,但是list子类化的类似情况不会产生错误,指出整数不可迭代:

class MyList(list):
    def __init__(self, y):
        print("Before")
        super().__init__() 
        print("After")


r = MyList(2)  # Prints "Before", "After"
Run Code Online (Sandbox Code Playgroud)

我不知道如何解决这个问题。我很少进行子类化,更罕见的是我对内置函数进行子类化,所以我一定在我的知识中出现了漏洞。这不是我期望子类化的工作方式。如果有人能解释这里发生了什么,我将不胜感激。


Python 3.9

Roo*_*Two 2

实例化一个类会导致__new__调用它的方法。它在构造函数调用中传递类的名称和参数。所以MyRandom([1, 2])结果是调用MyRandom.__new__(MyRandom, [1, 2]). (3.9.10 文档)。

因为没有MyRandom.__new__()方法,所以会搜索基类。 random.Random确实有一个__new__()方法(参见random_new()_randommodule.c )。所以我们接到这样的电话random_new(MyRandom, [1, 2])。

查看 的 C 代码random_new(),它调用random_seed(self, [1, 2]). 由于第二个参数不是 Null、orNone或intint 的子类,因此代码调用PyObject_Hash([1, 2]). 但列表不可散列,因此会出现错误。

如果__new__()返回类的实例,则__init__()使用构造函数调用中的参数来调用该方法。

一种可能的解决方法是定义一个MyRandom.__new__()方法,该方法调用super().__new__()但仅传递适当的参数。

class MyRandom(random.Random):
    def __new__(cls, *args, **kwargs):
        #print(f"In __new__: {args=}, {kwargs=}")

        # Random.__new__ expects an optional seed. We are going to 
        # implement out own RNG, so ignore args and kwargs. Pass in a 
        # junk integer value so that Random.__new__ doesn't waste time
        # trying to access urandom or calling time to initialize the MT RNG
        # since we aren't going to use it anyway.
        return super().__new__(cls, 123)
    
    def __init__(cls, *args, **kwargs):
        #print(f"In __init__: {args=}, {kwargs=}")

        # initialize your custom RNG here
        pass
Run Code Online (Sandbox Code Playgroud)

还可以重写以下方法:random()、seed()、getstate()、setstate()和(可选)getrandbits()。

__init__()另一种修复方法是仅在子类的方法中使用关键字参数。random_new()用于检查是否正在创建 random.Random 实例的C 代码。如果为 true,则如果存在任何关键字参数,代码将引发错误。但是,如果正在创建子类,则任何关键字参数都会被 忽略random_new(),但可以在子类中使用__init__()。

class MyRandom(random.Random):
    def __init__(self, *, x):  # make x a keyword only argument
        print("Before")
        super().__init__()  # Nothing passed to parent
        print("After")

MyRandom(x=[])
Run Code Online (Sandbox Code Playgroud)

有趣的是,在 Python 3.10 中,代码已更改为在提供超过 1 个位置random_new参数时引发错误。