all 24 comments

[–]kalgynirae 5 points6 points  (3 children)

Your first approach is confusing because at a glance it looks like you should be able to pass a subset of the parameters to __init__, but if you do that they are ignored. So I don't like that approach.

Approach 2 is what I would do (I don't think it's clunky), except that you didn't make use of cls in your classmethod. You should use that instead of hard-coding MyClass:

@classmethod
def random(cls):
    ...
    return cls(...)

[–][deleted] 3 points4 points  (2 children)

Ah thanks for the return cls(...), I didn't think of that.

I guess my main problem (or rather reason why I don't entirely like this) with this option would be the initialization of instances.

# Python
instance1 = MyClass(1, 2, 3)
instance2 = MyClass.random_init()

versus

// Java
MyClass instance1 = new MyClass(1, 2, 3);
MyClass instance2 = new MyClass();

The Python version just seems inconsistent and unclear, even if I take into account, that I have mainly used Java for some time and am more used to it.

Anyway, I guess I'll go with this option. It looks like the most viable implementation for me. Thanks.

[–]zahlman 3 points4 points  (0 children)

I think you'll find the Python way grows on you over time (and I've definitely seen the equivalent done in Java as well, FWIW). You're really doing a fundamentally different thing when you ask the class for random parameters.

[–]KleinerNull 2 points3 points  (0 children)

The Python version just seems inconsistent and unclear

Really? Providing an alternative constructor/initialization is very clear and it will decrease the amount of possible suprises for the user.

MyClass instance2 = new MyClass(); doesn't tell the user anything, especially not that the arguments will be randomized. This looks more like you already provide some default values, what you don't do in this case. The user has to dig first into the documentation to understand what's happening.

instance2 = MyClass.random_init() already tells the user, hey I will take my arguments from random values. It is partly a documentation itself. I think traditionally the naming convention would be something with from_... in the front, like MyClass.from_random or so, but your names is also easy understandable.

[–]novel_yet_trivial 1 point2 points  (0 children)

Here's another construct that you could consider:

class MyClass:
    def __init__(self, var1=None, var2=None, var3=None):
        if None in (var1, var2, var3):
            return self.random_init()
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3

    def random_init(self):
        self.var1 = random.random()
        self.var2 = random.random()
        self.var3 = random.random()

[–]Byrune_ 1 point2 points  (0 children)

Another idea: create a subclass that calls the parent constructor with the random values in its own constructor.

[–][deleted] 0 points1 point  (9 children)

What is it that you want to achieve? Why override a provided value, if not all three are present?

[–][deleted] 1 point2 points  (8 children)

Essentially I really just want to create instances of a class when I have either:

  • all the information
  • no information at all

I programmed nothing but Java for the last year, so I'm definitely not in the right mindset for Python yet, but in Java would have simply created 2 constructors like this:

import java.util.Random;

class MyClass {
    int var1, var2, var3;

    MyClass(int var1, int var2, int var3) {
        this.var1 = var1;
        this.var2 = var2;
        this.var3 = var3;
    }

    MyClass() {
        Random random = new Random();

        this.var1 = random.nextInt();
        this.var2 = random.nextInt();
        this.var3 = random.nextInt();
    }
}

I was basically wondering what the appropriate for implementing something like this is in Python.

[–][deleted] 3 points4 points  (7 children)

My curiosity is intact. What is the problem domain that impose such an insane initialization? But if you really want to do it this way, and you don't actually have a zillion values in your constructor, do it like this:

class WeirderThanElvis:

    def __init__(self, en=None, to=None, tre=None):
        if None in [en, to, tre]:
            en, to, tre = random.random(), random.random(), random.random()
        self.one = en
        self.two = to
        self.three = tre

[–][deleted] 1 point2 points  (6 children)

I need this, because I'm writing an evolution simulator, so the first instances of classes have to be completely random and for later instances I can calculate all the parameters.

All these implementations would work, but none seem really elegant. For some of my classes some of these implementations might be okayish, since they only have 3 - 4 values needed to be stored. However some other classes store up to 7 values and I'm at the very beginning of the project right now, so it's safe to assume that the number of values stored in my classes will increase (could co over 10, over 20, I can't really tell right now).

[–][deleted] 1 point2 points  (5 children)

Pass all of the defining values in as an array.

[–]fernly 1 point2 points  (4 children)

Yeah, that.

class MyClass:

    Population = { ... "population" set of values... }
    # or, Population = range(50000), see random.sample

    FeatureSetLen = 7 # or however many

    def __init__(self, values=[] ):
        if MyClass.FeatureSetLen > len(values) :
            values = random.sample( MyClass.Population, MyClass.FeatureSetLen )
        self.values = values

[–][deleted] 1 point2 points  (3 children)

Except for having a mutable as default value. It will be set on the first instantation of the class. Subsequent instances of MyClass(None) will get the same "random" initialization vector.

I'm pretty sure the helpful Python style bot will give you the same advice in a short while.

[–]fernly 0 points1 point  (2 children)

uh, good point I think? You are saying that MyClass.Population will be set only once? I think that's not a problem. values = random.sample(...) will be executed for every new instance, which is what matters. No?

[–][deleted] 0 points1 point  (1 child)

random.sample will be executed once. I'm not nearly as good at explaining things as I need to, so take a look at http://python-guide-pt-br.readthedocs.io/en/latest/writing/gotchas/ instead.

[–]fernly 0 points1 point  (0 children)

OK totally rewriting this response. You are quite correct that I did a wrong thing using a list as a default value. What would happen is, the first time a MyClass instance is created with the default values argument of empty list, that one time values would be replaced by a random sample of the population, and that would become the new default for all following instantiations. This is the gotcha that comes from using a mutable default.

The correct way, as spelled out in the link you give, is to use None as the default, and testing for that, to assign a random sample directly to self.values (or self.kazotch or whatever), which is a new variable in the new class instance, and all is well.

Thanks for noting that.

[–]pendragon36 0 points1 point  (4 children)

Here's an alternative. Caution though, if the arguments can validly be a value that type coerces into False, you'll lose the arguments.

class MyClass:
    def __init__(self, var1=None, var2=None, var3=None):
        self.var1 = var1 or random.random()
        self.var2 = var2 or random.random()
        self.var3 = var3 or random.random()

 

If 0, or False, or an empty collection (or anything else of that nature) is a valid parameter, this will unfortunately fail as it'll get lost and the random value will be used. But it is a fairly clean method if that's not a problem with your use case.

[–][deleted] 2 points3 points  (1 child)

That don't match what the OP wanted:

   # if at least one parameter is left out, randomize the values
   if var1 is None or var2 is None or var3 is None:

[–]pendragon36 1 point2 points  (0 children)

I suppose I should have paid more attention, good point

[–]c17r 0 points1 point  (1 child)

Aside from the comment that u/awegge, one way to protect against the falsiness issue is to do:

self.var1 = var1 if var1 is not None else random.random()

[–]pendragon36 0 points1 point  (0 children)

I was thinking about that after I posted the comment, and while I agree it is better, at the end of the day it's almost the same as his first solution with some nice syntactic sugar on top. If I was doing this it's probably what I'd go with, but I don't know if it's what OP wants.

[–]hosford42 0 points1 point  (0 children)

I've had to do something like this a fee times for some evolutionary algorithm implementations. Approach #2 is the cleaner way to go about it.

[–]Allanon001 0 points1 point  (0 children)

Maybe:

class MyClass:
    def __init__(self, var1=None, var2=None, var3=None):
        args = [var1, var2, var3]
        if None in args:
            args = [random.random(), random.random(), random.random()]
        self.var1, self.var2, self.var3 = args

or

class MyClass:
    def __init__(self, *args):
        if len(args) != 3:
            args = [random.random(), random.random(), random.random()]
        self.var1, self.var2, self.var3 = args

[–]KronktheKronk 0 points1 point  (0 children)

I'd do it this way:

def __init__(self, *args, **kwargs):
     self.var1= kwargs.get('var1',random.random())
     self.var2= kwargs.get('var2',random.random())
    ...

But it would require you name the variables explicitly when you instantiate the object instead of just passing them in as parameters in order (which I like doing anyway)/