Me gustaría construir un objeto que funcione como un generador de números aleatorios, pero que genere números en una secuencia específica.
# 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()) El problema con el código anterior es que no puedo llamar a nrng en la última línea porque es un generador. Sé que la forma más sencilla de reescribir el código anterior es simplemente recorrer los números no aleatorios en lugar de definir el generador. Preferiría que el ejemplo anterior funcione porque estoy trabajando con una gran parte del código que incluye una función que acepta un generador de números aleatorios como argumento, y me gustaría agregar la funcionalidad para pasar secuencias de números no aleatorios sin reescribir el código completo.
EDITAR: Veo cierta confusión en los comentarios. Soy consciente de que los generadores de números aleatorios de Python generan números pseudoaleatorios. Esta publicación trata sobre la sustitución de un generador de números pseudoaleatorios por un generador de números que genera números a partir de una secuencia no aleatoria especificada por el usuario (por ejemplo, un generador que genera la secuencia 1,1,2,2,1,0,1 si quiero que lo haga).
Puede llamar a next() con un generador o iterador como argumento para retirar exactamente un elemento de él. Guardar el generador en una variable de antemano le permite hacer esto varias veces.
# 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 puede recordar el último número que generó porque es una función de la clase np.random.RandomState . Numpy simplemente crea un alias para el método de clase para que sea accesible directamente desde el módulo np.random en lugar de tener que acceder a él a través de la clase.
Sabiendo esto, puede escribir su propia clase para que funcione así:
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 classEntonces, puedes hacer:
print(nrng()) # 1.0 print(nrng()) # 1.5 print(nrng()) # 2.0 Si desea poder tener varias instancias simultáneas de nrng , puede hacer que nrng() un método de instancia en lugar de un método de clase:
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 Luego, puede usar nrng() para hacer referencia al método que está vinculado a la instancia de NotRandom que creó. Si desea otra instancia, también puede tenerla:
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.5Editar:
La forma más limpia de hacer esto sería usar una lambda para ajustar su llamada a next(nrng) según el gran comentario de @GACy20:
def nrng_gen(): yield from range(10) nrng = nrng_gen() nrng_func = lambda: next(nrng) for i in range(10): print(nrng_func())Respuesta original:
Si desea que su objeto mantenga el estado y se vea como una función, cree una clase personalizada con el método __call__ .
p.ej.
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())Sin embargo, no recomendaría esto a menos que sea absolutamente necesario, ya que oscurece el hecho de que su nrng mantiene un estado (aunque técnicamente, la mayoría de los rng mantienen su estado internamente).
Lo mejor es usar un generador regular con yield llamando a continuación o escribir un iterador personalizado (también basado en clases). Esos funcionarán con cosas como bucles for y otras herramientas de Python para la iteración (como el excelente paquete itertools).
función que acepta un generador de números aleatorios como argumento
Llámalo así entonces:
that_function(nrng().__next__) O con functools.partial :
that_function(partial(next, nrng())) O sin tu generador, si ese arange es todo lo que quieres:
that_function(iter(np.arange(1,10.5,0.5)).__next__) Código de demostración ( rng y nrng son suyos, agregué that_function y las llamadas de prueba):
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()))Salida ( ¡Pruébelo en línea! ):
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.5La solución más simple para hacer que un generador sea invocable es crear un método enlazado para __next__ :
>>> def f(): yield 10 yield 20 yield 30 >>> g = f().__next__ >>> g() 10 >>> g() 20 >>> g() 30Estoy de acuerdo, next() parece el camino a seguir. Honestamente, simplemente habría hecho algo como esto:
# 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)