Learn to Code via Tutorials on Repl.it!

← Back to all posts
An implementation and explanation of a `with` construction in Python 3.
Th3OneAndOnly (6)

(Yay first post! Anyways...)

When defining classes in Python that the user is meant to define functions with, your choices are limited. Either you do something like this:

from types import MethodType
t = ExampleClass()
def code_block(self):
  print("Something idk lame example.")
def other_block(self):
  print("Another lame example.")
t.code_block = MethodType(code_block, t)
t.other_block = MethodType(other_block, t)

which is long and tedious, or you can do some form of definition in the constructor:

from types import MethodType
def c_block(self):
  print("Something idk even lamer example.")
def o_block(self):
  print("Another even lamer example.")
t = ExampleClass(code_block=c_block, other_block=o_block)

which is fine, but for multiple methods it quickly gets tedious.
So what's the fix? You can't do multi-line lambdas... or can you? Bill Mill says you can. And by adapting his code we can get what we want. Today we'll create a with constructor that allows for a quick and easy way to define selective methods, like this:

from types import MethodType
t = ExampleClass()
with t:
  def code_block(self):
    print("Something not lame hopefully.")
  def other_block(self):
    print("Another lame example.")

Which is both smaller and more concise and clear.

First, we need to decipher Bill's code and understand how this applies to our situation. He has a way to extract local variables and functions out of a with block. That's all we need, not the decorator (although one will be involved). So go take a look at his website for the nitty-gritty of how we extract the local variables, and we'll create a class to wrap our with:

import inspect
from types import MethodType
class Base:
    def __enter__(self):
        self.stored_locals = dict(inspect.currentframe().f_back.f_locals)

    def __exit__(self, type, value, tb):
        stored_locals = inspect.currentframe().f_back.f_locals
        clocals = {
            i: stored_locals[i]
            for i in stored_locals if (i not in self.stored_locals) or (
                id(stored_locals[i]) != id(self.stored_locals[i]))

This is a shortened version of Bill's code, using a dictionary comprehension instead of the dual for loop in Bill's.

Now we need to take those and make them into our methods, but we don't necessarily need to be able to define them all. We should be able to define a function as allowed to be defined in a with statement or not. This is where the decorator comes in.

So methods in classes are just objects, and can contain attributes like any other object. In this way we can use setattr inside of a decorator to mark functions as... we'll say 'public' for lack of a better word (if there is please tell me I've yet to think of something better):

def public(func):
    setattr(func, "_public", True)
    return func

Now in our class we need to go through our attributes and extract those that are public:

publics = [
  i for i in dir(self)
  if getattr(getattr(self, i, None), "_public", False)
[setattr(self, c, MethodType(clocals[c, self)) for c in clocals if c in publics]

With this in place, we're done! Now we can make a class that inherits this one and use @public to specify a function as public.

Thank you, repl.it users, for reading this. I hope to do more cool programming stuff and highlight interesting and helpful tricks here! Full code can be found with the attached repl.
EDIT: Totally forgot to put the proper imports there so that might've been confusing.