Присвоение случайного значения параметру в программе Python - PullRequest
5 голосов
/ 24 июня 2011

Мне нужно назначить случайное значение по умолчанию в __init__().Например:

import math
import random
class Test:
    def __init__(self, r = random.randrange(0, math.pow(2,128)-1)):
        self.r = r
        print self.r

Если я создам 10 экземпляров Test, все они получат одно и то же случайное значение.Я не понимаю, почему это происходит.Я знаю, что могу присвоить случайное значение внутри __init__(), но мне любопытно, почему это происходит.Мое первое предположение состояло в том, что начальное значение - это текущее время, а объекты создаются слишком близко друг к другу, и в результате получают одно и то же случайное значение.Я создал объекты с интервалом в 1 секунду, но результат все тот же.

Ответы [ 4 ]

15 голосов
/ 24 июня 2011

Значение параметра по умолчанию устанавливается во время создания функции, а не при ее вызове - поэтому каждый раз оно одинаково.

Типичный способ справиться с этим - установитьпараметр по умолчанию None и проверить его с помощью оператора if.

import math
import random
class Test:
     def __init__(self, r = None):
             if r is None:
                 r = random.randrange(0, math.pow(2,128)-1)
             self.r = r
             print self.r
4 голосов
/ 24 июня 2011

random.randrange(0, math.pow(2,128)-1) вычисляется, когда функция определена, а не когда она вызывается.

Использование

class Test:
    def __init__(self, r = None):
        if r is None:
            r = random.randrange(0, math.pow(2,128)-1)
        self.r = r
        print self.r

вместо.

1 голос
/ 24 июня 2011

Причина, по которой все экземпляры вашего класса имеют одинаковое значение для аргумента, заключается в том, что его значение по умолчанию определяется только один раз, когда определение метода, частью которого он является, компилируется как часть выполнения оператора class.

Хотя это не то использование, для которого оно было разработано, вы можете использовать рецепт 20.14 под названием Автоматическая инициализация атрибутов экземпляра из несколько устаревшей Python Cookbook, 2nd Edition чтобы достичь того, что вы хотите сделать.

Это применимо к примеру кода в вашем вопросе:

class AutoAttr(object):
    def __init__(self, name, factory, *args, **kwds):
        self.data = name, factory, args, kwds
    def __get__(self, obj, cls=None):
        name, factory, args, kwds = self.data
        setattr(obj, name, factory(*args, **kwds))
        return getattr(obj, name)

import math
import random

class Test(object):
    r = AutoAttr('r', random.randrange, 0, math.pow(2,128)-1)  # default value
    def __init__(self, r=None):
        if r is not None:  # argument value supplied to override default?
            self.r = r
        print format(self.r, ',d')

for i in xrange(5):
    Test()
Test(42)  # override default random initial value

Пример вывода:

282,608,676,427,101,189,083,121,399,193,871,110,434
211,475,719,281,604,076,410,306,973,803,289,140,631
86,842,148,927,120,143,765,936,219,265,140,532,918
41,767,122,731,332,110,507,836,985,804,081,250,336
97,993,619,669,833,151,963,441,072,354,430,500,011
42

Вот какэто работает:

Класс auto_attr из рецепта называется дескриптором .Один из них назначен атрибуту Test class с именем r.При первом обращении к этому атрибуту в экземпляре Test с использованием self.r Python замечает, что он связан с дескриптором, и вызывает его метод __get__() с экземпляром Test в качестве аргумента obj.

Метод дескриптора __get__() вызывает связанную фабричную функцию и присваивает результат атрибуту instance с тем же именем, так что все последующие ссылки на этот атрибут через экземпляр получат его фактическое значение вместодескриптор Test класса *1037*.

1 голос
/ 24 июня 2011

Когда вы помещаете выражение в заголовок конструктора (вместо тела конструктора), оно вычисляется только один раз.

Попробуйте это в теле:

self.r = random.randrange(0,math.pow(0,128)-1)
...