I have been learning python as my first programing language over the last few months, and up until now I felt like I had a decent understanding of functions, classes, and methods. However recently I was looking through python's random module and came across something that I cant quite figure out.
def randint(self, a, b):
#Return random integer in range [a, b], including both end points.
return self.randrange(a, b 1)
The above method is from the random module and is part of the Random() class, it is used to generate a random number between a and b. Because it's a method, it needs to be called on a class instance to satisfy the requirement for the 'self' argument.
import random
x = random.Random()
print(x.randint(1, 6))
In the above second block of code I am using this method to generate a random number between 1 and 6. This works as I expected it to because the variable x is being made into an instance of the Random() class, and then the randint() method is called on that instance with 1 and 6 passed in as the arguments a and b.
import random
print(random.randint(1, 6))
what I cant quite understand however is why this third block of code also works and achieves the same result. In this third example, there is no instance of the Random() class being created, and therefore nothing to satisfy the required "self" argument. I would have expected this to give me an error saying that 3 arguments are required (self, a, and b) but only 2 were provided (a and b). It is almost like this method is being treated like a regular function now and doesn't need to be called on a class instance anymore. Why is this? Is there something that I am missing here?
CodePudding user response:
At the end of random.py
a bunch of regular functions are defined to call methods on a generic instance:
_inst = Random()
seed = _inst.seed
random = _inst.random
uniform = _inst.uniform
triangular = _inst.triangular
randint = _inst.randint
choice = _inst.choice
randrange = _inst.randrange
sample = _inst.sample
shuffle = _inst.shuffle
choices = _inst.choices
normalvariate = _inst.normalvariate
lognormvariate = _inst.lognormvariate
expovariate = _inst.expovariate
vonmisesvariate = _inst.vonmisesvariate
gammavariate = _inst.gammavariate
gauss = _inst.gauss
betavariate = _inst.betavariate
paretovariate = _inst.paretovariate
weibullvariate = _inst.weibullvariate
getstate = _inst.getstate
setstate = _inst.setstate
getrandbits = _inst.getrandbits
randbytes = _inst.randbytes
So random.randint(...)
is equivalent to random._inst.randint(...)
. But you can't write that because names beginning with _
are not exported.
CodePudding user response:
In this third example, there is no instance of the Random() class being created, and therefore nothing to satisfy the required "self" argument.
This is an incorrect assertion. Random
is a somewhat special case in that the module itself provides these methods as quasi-static methods when the module is loaded (in addition to exposing them as instance methods) by creating a general-use instance straight off the bat and exposing that instance’s methods for use without an explicit instantiation in your top-level script.
You can find this (along with a helpful comment explaining this design choice) in the source code for Random.py:788-793
:
# ----------------------------------------------------------------------
# Create one instance, seeded from current time, and export its methods
# as module-level functions. The functions share state across all uses
# (both in the user's code and in the Python libraries), but that's fine
# for most programs and is easier for the casual user than making them
# instantiate their own Random() instance.
Using these methods does limit you to using the current time as a seed for randomness, but that is generally sufficient for most use cases.