Skip to main content

Expose the inner scope of functions

Project description

Innerscope

conda-forge pypi PyPI - Python Version License Tests Coverage

innerscope exposes the inner scope of functions and offers primitives suitable for creating pipelines. It explores a design space around functions, dictionaries, and classes.

To install with pip: pip install innerscope

To install with conda: conda install -c conda-forge innerscope

A function can be made to act like a dictionary:

@innerscope.call
def info():
    first_name = 'Erik'
    last_name = 'Welch'
    full_name = f'{first_name} {last_name}'
    return 'success!'

>>> info['first_name']
'Erik'
>>> info['full_name']
'Erik Welch'
>>> info.return_value
'success!'

Sometimes we want functions to be more functional and accept arguments:

if is_a_good_idea:
    suffix = 'the amazing'
else:
    suffix = 'the bewildering'

@innerscope.callwith(suffix)
def info_with_suffix(suffix=None):
    first_name = 'Erik'
    last_name = 'Welch'
    full_name = f'{first_name} {last_name}'
    if suffix:
        full_name = f'{full_name} {suffix}'

>>> info_with_suffix['full_name']
'Erik Welch the bewildering'

Cool!

But, what if we want to reuse the data computed in info? We can control exactly what values are within scope inside of a function (including from closures and globals; more on these later). Let's bind the variables in info to a new function:

@info.bindto
def add_suffix(suffix):
    full_name = f'{first_name} {last_name} {suffix}'

>>> scope = add_suffix('the astonishing')
>>> scope['full_name']
'Erik Welch the astonishing'

add_suffix here is a ScopedFunction. It returns a Scope, which is the dict-like object we've already seen.

scoped_function ftw!

Except for the simplest tasks (as with call and callwith above), using scoped_function should usually be preferred.

# step1 becomes a ScopedFunction that we can call
@scoped_function
def step1(a):
    b = a + 1

>>> scope1 = step1(1)
>>> scope1 == {'a': 1, 'b': 2}
True

# Bind any number of mappings to variables (later mappings have precedence)
@scoped_function(scope1, {'c': 3})
def step2(d):
    e = max(a + d, b + c)

>>> step2.outer_scope == {'a': 1, 'b': 2, 'c': 3}
True
>>> scope2 = step2(4)
>>> scope2 == {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
True
>>> scope2.inner_scope == {'d': 4, 'e': 5}
True

Suppose you're paranoid (like me!) and want to control whether a function uses values from closures or globals. You're in luck!

global_x = 1

def f():
    closure_y = 2
    def g():
        local_z = global_x + closure_y
    return g

# If you're the trusting type...
>>> g = f()
>>> innerscope.call(g) == {'global_x': 1, 'closure_y': 2, 'local_z': 3}
True

# And for the intelligent...
>>> paranoid_g = scoped_function(g, use_closures=False, use_globals=False)
>>> paranoid_g.missing
{'closure_y', 'global_x'}
>>> paranoid_g()
- UserWarning: Undefined variables: 'global_x', 'closure_y'.
- Perhaps use `bind` method to assign values for these names before calling.
>>> new_g = paranoid_g.bind({'global_x': 100, 'closure_y': 200})
>>> new_g.missing
set()
>>> new_g() == {'global_x': 100, 'closure_y': 200, 'local_z': 300}
True

How?

This library does not use exec, eval, the AST, or source code. It runs on CPython, PyPy, and Stackless Python. You should feel comfortable using innerscope. It actually offers two methods for obtaining the inner scope, and both are very reliable. Of course we're doing something magical under the hood, and I would love to explain how some day.

Why?

It's all @mrocklin's fault for asking a question. innerscope is exploring a data model that could be convenient for running code remotely with dask. I bet it would even be useful for building pipelines with dask. I'm sure there are other creative uses for it just waiting to be discovered. Update: and afar has been born!

This library is totally awesome and you should use it and tell all your friends 😉 !

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

innerscope-0.7.0a0.tar.gz (22.8 kB view details)

Uploaded Source

Built Distribution

innerscope-0.7.0a0-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file innerscope-0.7.0a0.tar.gz.

File metadata

  • Download URL: innerscope-0.7.0a0.tar.gz
  • Upload date:
  • Size: 22.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.8

File hashes

Hashes for innerscope-0.7.0a0.tar.gz
Algorithm Hash digest
SHA256 f23394b0582e602d63fda2bebcbaf0b5fe9a44a07ab2bf0634ef1b2566c5910a
MD5 78887157b58f61854887f07af0a4542b
BLAKE2b-256 71f6957c2c3f3f55d4a6c36ac33938df21cb893ffc6fbcc29a59ae934e9041ff

See more details on using hashes here.

Provenance

File details

Details for the file innerscope-0.7.0a0-py3-none-any.whl.

File metadata

  • Download URL: innerscope-0.7.0a0-py3-none-any.whl
  • Upload date:
  • Size: 19.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.8

File hashes

Hashes for innerscope-0.7.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 38b2f416f23b446da8230c519d8a8d429630108e5d167e42a2d61cb5ae1edbcd
MD5 1c6de41dac20efd6f019cb07b9be6b79
BLAKE2b-256 2678e67cc379b8863d848010ff8da70abdceec6897024a7440eb1201b0ce934a

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page