我怎样才能创建一个具有类似随机数生成器接口但实际上生成指定序列的对象?

How can I make an object with an interface like a random number generator, but that actually generates a specified sequence?

我想构建一个像随机数生成器一样工作的对象,但会按指定的顺序生成数字。

# a random number generator
rng = lambda : np.random.randint(2,20)//2

# a non-random number generator
def nrng():
    numbers = np.arange(1,10.5,0.5)
    for i in range(len(numbers)):
        yield numbers[i]

for j in range(10):
    print('random number', rng())
    print('non-random number', nrng())

上面代码的问题,我无法在最后一行调用 nrng,因为它是一个生成器。我知道重写上面代码的最直接的方法是简单地循环非随机数而不是定义生成器。我更愿意让上面的例子工作,因为我正在处理大量代码,其中包括一个接受随机数生成器作为参数的函数,我想添加功能来传递非随机数序列而不重写完整代码。

编辑:我看到评论中有些混乱。我知道 python 的随机数生成器会生成伪随机数。 post 是关于用数字生成器替换伪随机数生成器,该数字生成器从 非随机、用户指定的 序列(例如,生成数字序列 1,1,2,2,1,0,1 如果我想要的话)。

您可以使用生成器或迭代器作为参数调用 next() 以从中提取一个元素。事先将生成器保存到一个变量中,您可以多次执行此操作。

# make it a generator
def _rng():
    while True:
        yield np.random.randint(2,20)//2

# a non-random number generator
def _nrng():
    numbers = np.arange(1,10.5,0.5)
    for i in range(len(numbers)):
        yield numbers[i]

rng = _rng()
nrng = _nrng()
for j in range(10):
    print('random number', next(rng))
    print('non-random number', next(nrng))

np.random.randint 可以记住它生成的最后一个数字,因为它是 np.random.RandomState class 的函数。 Numpy 只是给 class 方法起了别名,这样它就可以直接从 np.random 模块访问,而不是让你通过 class.

访问它

了解这一点,您可以编写自己的 class 来像这样工作:

class NotRandom:

    numbers = np.arange(1,10.5,0.5)
    last_index = -1

    @classmethod
    def nrng(cls):
        cls.last_index += 1
        if cls.last_index < len(cls.numbers):
            return cls.numbers[cls.last_index]
        # else:
        return None

# Create an alias to the classmethod
nrng = NotRandom.nrng      # Note this is OUTSIDE the class

那么,你可以这样做:

print(nrng())    # 1.0
print(nrng())    # 1.5
print(nrng())    # 2.0

如果您希望 nrng 有多个并发实例,您可以将 nrng() 设为 实例方法 而不是 class方法:

class NotRandom:
    def __init__(self):
        self.numbers = np.arange(1,10.5,0.5)
        self.last_index = -1

    def nrng(self):
        self.last_index += 1
        if self.last_index < len(self.numbers):
            return self.numbers[self.last_index]
        # else:
        return None

# Create an object. Then create an alias to its method
nrng = NotRandom().nrng 

然后,您可以使用 nrng() 来引用绑定到您创建的 NotRandom 实例的方法。如果你想要另一个实例,你也可以拥有它:

another_notrandom = NotRandom()
nrng2 = another_notrandom.rng

print(nrng())    # 1.0
print(nrng())    # 1.5

print(nrng2())   # 1.0

print(nrng())    # 2.0

print(nrng2())   # 1.5

编辑:

根据@GACy20 的精彩评论,最简洁的方法是使用 lambda 来包装对 next(nrng) 的调用:

def nrng_gen():
    yield from range(10)

nrng = nrng_gen()

nrng_func = lambda: next(nrng)

for i in range(10):
    print(nrng_func())

原回答:

如果您希望您的对象保持状态并看起来像一个函数,请使用 __call__ 方法创建自定义 class。

例如

class NRNG:
    def __init__(self):
        self.numbers = range(10)
        self.state = -1
    def __call__(self):
        self.state += 1
        return self.numbers[self.state]
        
nrng = NRNG()


for i in range(10):
    print(nrng())

但是,除非绝对必要,否则我不推荐这样做,因为它掩盖了您的 nrng 保持状态的事实(尽管从技术上讲,大多数 rng 在内部保持其状态)。

最好只使用带有 yield 的常规生成器,方法是在其上调用 next 或编写自定义迭代器(也 class-based)。这些将与 for 循环和其他 python 迭代工具(如优秀的 itertools 包)一起使用。

function that accepts a random number generator as an argument

然后这样称呼它:

that_function(nrng().__next__)

functools.partial:

that_function(partial(next, nrng()))

或者没有你的发电机,如果 arange 就是你想要的:

that_function(iter(np.arange(1,10.5,0.5)).__next__)

演示代码(rngnrng 是你的,我添加了 that_function 和测试调用):

import numpy as np
from functools import partial

# a random number generator
rng = lambda : np.random.randint(2,20)//2

# a non-random number generator
def nrng():
    numbers = np.arange(1,10.5,0.5)
    for i in range(len(numbers)):
        yield numbers[i]

def that_function(rng):
    print(*(rng() for j in range(10)))

that_function(rng)
that_function(nrng().__next__)
that_function(iter(np.arange(1,10.5,0.5)).__next__)
that_function(partial(next, nrng()))

输出(Try it online!):

4 6 1 3 8 7 3 6 2 1
1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5
1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5
1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5

使生成器可调用的最简单解决方案是为 __next__ 创建一个绑定方法:

>>> def f():
        yield 10
        yield 20
        yield 30
    
>>> g = f().__next__
>>> g()
10
>>> g()
20
>>> g()
30

我同意,next() 似乎是可行的方法。老实说,我会简单地做这样的事情:

# Get the first n numbers (10 in this case)

for i,j in enumerate(nrng()):  
    if i > 9:
        break
    print('random number', rng())
    print('non-random number', j)
    
# Or this if you want to get all numbers in nrng()

for j in nrng():
    print('random number', rng())
    print('non-random number', j)