I have encountered a conundrum. I think I should know how to solve this — I am extremely knowledgeable and experienced in Python — but I can't figure it out. It seems like what I am struggling with should be addressed by a design pattern that is a variation on the factory theme, but I can't recall one. (At the end of this writeup I suggest a technical solution, but it seems an implausible one.) I hope you find this discussion interesting in its own right, but I look forward to hearing some suggestions for solutions to my problem.
Given classes, A, B, C, and D, I want to limit creation of B instances to methods of A, C instances to methods of B, and D instances to methods of C — in other words A instances are the factories for B instances, B instances the factories for C instances, and C instances the factories for D instances. (I also want each instance of D to store which instance of C created it, each instance of C to store which instance of B created it, and each instance of B to store which instance of A created it, but this is easily arranged by having each instance provide itself as an __init__
argument when it creates an instance of the next class down in the hierarchy.)
These are model-layer classes that an application manipulates. There is no problem with the application manipulating instances of B, C, or D, it just shouldn't create them directly — all it should be able to create directly are instances of A (and A might even be a singleton). There are various kinds of validation, management, tracking, auditing, etc. that could be implemented in the methods of one class that create instances of the next.
An example might be a Operating System that can create Filesystems that can create Directories that can create Files, but application code would have to follow that chain. For example, even if it had its hands on a Directory, it shouldn't be able to create a File giving File.__init__
the Directory instance even though that is what instances of Directory would do when asked to create a File.
I am looking for a design solution backed up some implementation, not something user-proof -- I understand the futility of the latter.
The only thing I have thought of so far is:
Module-level "hiding" by omitting the class from the module's __all__
list is insufficient, because the only affects import *
constructions — another module could still reach the class by import module
then referencing module.class
.
(This is all vaguely reminiscent of the C++ problems that require two classes to be friends because they participate in a two-way relationship: instances of each class must be able to reference the methods of the other class that manage that the other side of the relationship.)
The solution that may best conform to the semantics and pragmatics of Python is to define D inside C, C defined inside B, and B defined inside A. This seems an awfully ugly way to cram multiple modules worth of code into a single very large class. (And imagine if the chain went down a few more levels.) This is an extension of more common uses of nested classes and maybe the only technically sound way of doing this, but I have never seen this sort of Russian-doll class structure.
Ideas?
Python is not very good at completely hiding it's objects ... Even if you decide to do the "Russian doll" closure to attempt to hide a class definition, a user can still get at it...
class A(object):
def create_b(self):
class B(object):
pass
return B()
a = A()
b = a.create_b()
b_cls = type(b)
another_b = b_cls()
Simply put -- If a user has access to the source code (and they can get it via inspect
or at the very least, they can get the bytecode via dis
which to a trained eye is good enough), then they have the power to create instances of your class themselves if they're motivated enough.
Generally though, it's good enough to just document the class as being something that the user shouldn't instantiate themselves -- If they break that trust, it's their fault when their program crashes and burns (and has some nasty side-effect that causes their computer to catch fire after clearing their hard-drive). This is a pretty central python philosophy, stated nicely in the python mail archives:
Nothing is really private in python. No class or class instance can keep you away from all what's inside (this makes introspection possible and powerful). Python trusts you. It says "hey, if you want to go poking around in dark places, I'm gonna trust that you've got a good reason and you're not making trouble."
After all, we're all consenting adults here.
C++ and Java don't have this philosophy (not to the same extent). They allow you create private methods and static members.
Perl culture is like python in this respect, but Perl expresses the sentiment a bit differently. As the Camel book puts it,
"a Perl module would prefer that you stayed out of its living room because you weren't invited, not because it has a shotgun."
But the sentiment is identical.
Just from the top of my head:
def B(object):
pass
def D(object):
pass
def bound(object):
if type(object) is C:
assert isinstance(D)
if type(object) is A:
assert isinstance(B)
else:
assert false
@bound
def C(D):
pass
@bound
def A(B):
pass
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With