Overview
For the specific cheese example, I agree with many of the other answers about using default values to signal random initialization or to use a static factory method.  However, there may also be related scenarios that you had in mind where there is value in having alternative, concise ways of calling the constructor without hurting the quality of parameter names or type information.
Since Python 3.8 and functools.singledispatchmethod can help accomplish this in many cases (and the more flexible multimethod can apply in even more scenarios). (This related post describes how one could accomplish the same in Python 3.4 without a library.) I haven't seen examples in the documentation for either of these that specifically shows overloading __init__ as you ask about, but it appears that the same principles for overloading any member method apply (as shown below).
"Single dispatch" (available in the standard library) requires that there be at least one positional parameter and that the type of the first argument be sufficient to distinguish among the possible overloaded options. For the specific Cheese example, this doesn't hold since you wanted random holes when no parameters were given, but multidispatch does support the very same syntax and can be used as long as each method version can be distinguish based on the number and type of all arguments together.
Example
Here is an example of how to use either method (some of the details are in order to please mypy which was my goal when I first put this together):
from functools import singledispatchmethod as overload
# or the following more flexible method after `pip install multimethod`
# from multimethod import multidispatch as overload
class MyClass:
    @overload  # type: ignore[misc]
    def __init__(self, a: int = 0, b: str = 'default'):
        self.a = a
        self.b = b
    @__init__.register
    def _from_str(self, b: str, a: int = 0):
        self.__init__(a, b)  # type: ignore[misc]
    def __repr__(self) -> str:
        return f"({self.a}, {self.b})"
print([
    MyClass(1, "test"),
    MyClass("test", 1),
    MyClass("test"),
    MyClass(1, b="test"),
    MyClass("test", a=1),
    MyClass("test"),
    MyClass(1),
    # MyClass(),  # `multidispatch` version handles these 3, too.
    # MyClass(a=1, b="test"),
    # MyClass(b="test", a=1),
])
Output:
[(1, test), (1, test), (0, test), (1, test), (1, test), (0, test), (1, default)]
Notes:
- I wouldn't usually make the alias called overload, but it helped make the diff between using the two methods just a matter of which import you use.
- The # type: ignore[misc]comments are not necessary to run, but I put them in there to pleasemypywhich doesn't like decorating__init__nor calling__init__directly.
- If you are new to the decorator syntax, realize that putting @overloadbefore the definition of__init__is just sugar for__init__ = overload(the original definition of __init__).  In this case,overloadis a class so the resulting__init__is an object that has a__call__method so that it looks like a function but that also has a.registermethod which is being called later to add another overloaded version of__init__.  This is a bit messy, but it please mypy becuase there are no method names being defined twice.  If you don't care about mypy and are planning to use the external library anyway,multimethodalso has simpler alternative ways of specifying overloaded versions.
- Defining __repr__is simply there to make the printed output meaningful (you don't need it in general).
- Notice that multidispatchis able to handle three additional input combinations that don't have any positional parameters.