0

How to forbid direct object creation in Python 3?

class A:
    def create(self):
        return B()

class B:
    pass

b = B()  # FORBIDDEN

a = A()
a.create() # ALLOWED
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 10
    _Why_ do you want to do this? This is likely an [XY problem](https://meta.stackexchange.com/q/66377/248731). – jonrsharpe Jun 08 '22 at 10:14
  • 2
    You might try to define B.__init__(self, a = false) in your class B which raises an exception if a is false. In A.create() you can call B(true) instead of B(). So calling B() will raise an exception. Why would you want to do this anyway? – dVNE Jun 08 '22 at 10:16
  • 3
    You can never really *forbid* things in python, just eventually make it more annoying to do, but the real question is indeed why you would need that, looks like a X-Y problem ;) – mozway Jun 08 '22 at 10:16
  • I want only give that way api to instant B() to avoid some unwanted params for B(). – Ruslan Mansurov Jun 08 '22 at 10:21
  • 1
    Then maybe it would be better to check the params being passed? Please read the article linked about XY problem and try to ask about your ***real*** problem, not about the solution *you* think fits... – Tomerikoo Jun 08 '22 at 10:22
  • 1
    you can use the same approach as for creating a singleton (see https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python ) – XtianP Jun 08 '22 at 10:26
  • 1
    What unwanted params? Right now it doesn't define _any_, so e.g. `B(123, "foo")` would error out anyway. – jonrsharpe Jun 08 '22 at 10:29
  • 1
    We are all adults here. Use single leading underscore to indicate "internal use only" Check https://stackoverflow.com/q/551038/4046632 Still, this is bit weird as approach - unwanted params, etc. – buran Jun 08 '22 at 10:38
  • @mozway - Perhaps [this video](https://youtu.be/cKPlPJyQrt4?t=2426) will change your concepts about enforcing behaviour in python. – Michael Szczesny Jun 08 '22 at 12:14
  • @buran I would say that is the most Pythonic answer – juanpa.arrivillaga Jul 04 '22 at 06:37
  • @MichaelSzczesny what do you have in mind, specifically, with metaclasses that couldn't be subverted? Suppose you implement `__call__` to do something that prevents this... well, then I can do something like `foo = object.__new__(Foo); foo.__init__(bad, worse)`. Often, instead of going through a bunch of rigamarole, it is better to *simply document* how class should be used, e.g. in this case the class "private" by naming it with a single leading underscore, `class _B: ...` and avoid futilely adding complexity – juanpa.arrivillaga Jul 04 '22 at 06:38

1 Answers1

1

I am certain that whatever issue you are facing could be solved using an alternative approach that is more straigh forward and readable. However you can achieve what your ask by using the inspect module and force __init__ to throw an exception if it wasn't called from a specific classmethod.

for example:

import inspect

class A:
    def __init__(self):
        stack = inspect.stack()
        classvar, klas = list(stack[1].frame.f_locals.items())[0]
        if stack[1].function != 'create' and klas != type(self):
            raise Exception
   
    def __str__(self):
        return "Success"

    @classmethod
    def create(cls):
        result = cls()
        return result

a1 = A.create() # Returns instance of A
print(a1)       # prints "Success"

a2 = A()   # Throws Exception

I want to reiterate that this is likely not the best means of solving whatever your issue is.

Alexander
  • 16,091
  • 5
  • 13
  • 29