Skip to main content
This is a pre-production deployment of Warehouse. Changes made here affect the production instance of PyPI (pypi.python.org).
Help us improve Python packaging - Donate today!

Static variables for Python

Project Description

Static variables for Python

NOTE:

This is still very much a work in progress, and will segfault if you give it anything that is mildly complex. It will probably not work on any implementation except CPython.

Usage

static_variables

from static_variables import resolve_static

@resolve_static(static_variables={'counter': 0})
def f():
    counter += 1
    return counter

print(f())  # 1
print(f())  # 2
print(f())  # 3

The signature for static_variables is Mapping[str, Any], where the key is the name of the variable and the value is the initial value.

Also note that static variables will override global, nonlocal and local variables with the same name.

Set the value to static_variables.NO_VALUE to have no value in the beginning:

from static_variables import resolve_static, NO_VALUE

@resolve_static(staic_variables={'value': NO_VALUE})
def get_value():
    try:
        return value
    except NameError:
        value = could_be_anything()
        # `value` could also be `None`, so `None`
        # is not a sensible default.
        return value

get_value()  # Runs `could_be_anything`
get_value()  # Return the static value

static

from static_variables import static, resolve_static

# You don't really need to import `static`, it just stops
# IDEs from complaining.

@resolve_static
def f(to_add=None):
     ls = static([])
     if to_add is not None:
         ls.append(to_add)
     return ls

ls = f()
f(3)
assert ls == [3]  # True
assert ls is f()

Since Python variables are more like name tags, static will only really work well for mutable objects, like lists or sets.

For example, the following does not work:

@resolve_static
def f():
    counter = static(0)
    counter += 1
    return counter

assert f() == 1  # True
assert f() == 2  # False

You would have to use the static_variables argument to achieve this.

The static variable will always have the same id. They will refer to the same object, and is stored at the end of a function’s function.__code__.co_consts

Empty set literals

Since sets came after dictionaries, the {} literal is an empty dictionary. This changes that.

@resolve_static(empty_set_literal=True)
def f():
    return {}

assert f() == set()  # True
assert f() != {}  # True; {} is dict() in the outer scope.

You can also use EMPTY_SET to avoid turning all {} into empty sets.

from static_variables import resolve_static, EMPTY_SET

# Again, you don't need to import EMPTY_SET.
# It just stops IDEs from complaining.

@resolve_static(empty_set_literal=False)
def f():
    my_dict = {}
    my_set = EMPTY_SET  # Equivalent to `set()` but faster.
    return type(my_dict), type(my_set)

assert f() == (dict, set)  # True

Speed?

It would actually be faster to use static, as it delegates some processing to declaration time, instead of run time.

Take these two snippets:

def product_4(it):
    return itertools.product(it, repeat=4)

@resolve_static
def static_product_4(it):
    return static(itertools.product)(it, repeat=4)

And their disassembly:

product_4(it)
              0 LOAD_GLOBAL              0 (itertools)
              2 LOAD_ATTR                1 (product)
              4 LOAD_FAST                0 (it)
              6 LOAD_CONST               1 (4)
              8 LOAD_CONST               2 (('repeat',))
             10 CALL_FUNCTION_KW         2
             12 RETURN_VALUE
static_product_4(it)
              0 LOAD_CONST               3 (<class 'itertools.product'>)
              2 LOAD_FAST                0 (it)
              4 LOAD_CONST               1 (4)
              6 LOAD_CONST               2 (('repeat',))
              8 CALL_FUNCTION_KW         2
             10 RETURN_VALUE

The static version just loads the itertools.product constant, whilst the normal version looks up a global variable and an attribute on one.

Empty set literals and EMPTY_SET are equivalent and both faster than set().

They are not equivalent to static(set()) which would be faster, but it would be the same static set.

Installation

From PyPI

$ pip install static_variables

From source

$ git clone 'https://github.com/MitalAshok/static_variables.git'
$ python ./static_variables/setup.py install

How does it work?

static_variables

This creates a new variable in the closure of a function. The closure remains between function calls.

It replaces (LOAD|STORE|DELETE)_GLOBAL and (LOAD|STORE|DELETE)_FAST (local variables) opcodes in the bytecode with (LOAD|STORE|DELETE)_DEREF (load from the closure) ones.

static

The bytecode in Python is stack-based. resolve_static looks for a LOAD_GLOBAL 'static' opcode and then starts tracking what the size of the stack will be. When the stack size reaches 0 and a CALL_FUNCTION 1 (call the top of the stack with 1 item from below it on the stack) opcode is reached, it extracts the bytecode, creates a new function, and calls it to evaluate the bytecode. The whole static(...) is replaced with LOAD_CONST, to load a constant value which is appended to the code’s co_consts.

empty_set_literal

While iterating over the bytecode, if BUILD_MAP 0 is encountered (Create a new dictionary from the previous 0 items. i.e., an empty dictionary), it is replaced with BUILD_SET 0, which creates an empty set instead. This opcode still exists even though it doesn’t naturally occur so that it’s argument still correlates with the number of items to pop off of the stack to build the set with.

If a LOAD_GLOBAL 'EMPTY_SET' is encountered, it is always replaced with a BUILD_SET 0 (i.e., a new empty set.)

Release History

Release History

This version
History Node

0.0.4

History Node

0.0.3

History Node

0.0.2

History Node

0.0.1

Download Files

Download Files

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

File Name & Checksum SHA256 Checksum Help Version File Type Upload Date
static_variables-0.0.4.tar.gz (13.3 kB) Copy SHA256 Checksum SHA256 Source Aug 13, 2017

Supported By

WebFaction WebFaction Technical Writing Elastic Elastic Search Pingdom Pingdom Monitoring Dyn Dyn DNS Sentry Sentry Error Logging CloudAMQP CloudAMQP RabbitMQ Heroku Heroku PaaS Kabu Creative Kabu Creative UX & Design Fastly Fastly CDN DigiCert DigiCert EV Certificate Rackspace Rackspace Cloud Servers DreamHost DreamHost Log Hosting