Last active
December 16, 2015 07:53
-
-
Save binki/511a0f90c7c7ed4297af to your computer and use it in GitHub Desktop.
Encapsulation enforcement in python? weakref required
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
import abc | |
# Define an abstract class for our public interface. | |
class Human(object): | |
__metaclass__ = abc.ABCMeta | |
@abc.abstractmethod | |
def eat(self): | |
return NotImplemented | |
def _build_get_human(): | |
def get_human(name): | |
# Initialize state. Closures are readonly be default, so | |
# mutable state needs to use nonlocal | |
# (http://stackoverflow.com/a/2009645/429091). Because this is | |
# not compatible with python2, using the workaround at | |
# http://stackoverflow.com/a/28433571/429091 | |
class this: | |
meals_eaten = 0 | |
def eat(): | |
this.meals_eaten += 1 | |
print('%s is eating meal #%d…' % (name, this.meals_eaten)) | |
class ConcreteHuman(Human): | |
def eat(self): | |
return eat() | |
assert(issubclass(ConcreteHuman, Human)) | |
return ConcreteHuman() | |
return get_human | |
get_human = _build_get_human() | |
del _build_get_human | |
human = get_human('Joe') | |
assert(isinstance(human, Human)) | |
human1 = get_human('Binki') | |
human.eat() | |
human1.eat() | |
human1.eat() | |
for h in (human, human1): | |
h.eat() | |
# We can still discover the generated subclasses through | |
# __subclasses__(). But we cannot get to “this”. At least I hope there | |
# isn’t something like human.__closure__.this—does anyone know? | |
# | |
# Anyway, this brings us to the real problem of this attempted | |
# solution: it requires a new class definition for each | |
# instantiation. And because the superclass has a list of all | |
# subclasses, these will never get garbage collected(?). So, even though | |
# this seemed almost close to being acceptable, it simply | |
# isn’t. http://stackoverflow.com/a/22547695/429091 | |
print(Human.__subclasses__()) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# When first thinking of this problem, I supposed that something like | |
# weak references might exist in Python. And, in the end, I am | |
# figuring that the most readable and *only* technically feasible way | |
# to enforce encapsulation is to use weakref. Here goes. | |
import weakref | |
def Mug(): | |
# Have a mapping from each instance onto its state. That mapping | |
# will be inaccessible to outsiders as we’ll be referring to it | |
# via closure. This dictionary does not prevent instances from | |
# being garbage collected. This is basically the normal way to | |
# annotate “foreign” objects in python and should hopefully not be | |
# too big of a performance hit when used to enforce encapsulation. | |
these = weakref.WeakKeyDictionary() | |
class Mug: | |
def __init__(self, contents): | |
class this: | |
contents = '' | |
amount = 1 | |
this.contents = contents | |
these[self] = this | |
def pour(self): | |
this = these[self] | |
this.amount -= 0.2 | |
print('Pouring %s. %f remaining.' % (this.contents, this.amount)) | |
return Mug | |
Mug = Mug() | |
mug = Mug('tea') | |
mug1 = Mug('coffee') | |
mug.pour() | |
mug1.pour() | |
for m in (mug, mug1): | |
m.pour() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment