Skip to main content

The horrors of C Static variables for Python, with Python.

Project description

staticvar

A decorator that adds the horrors of C Static variables to Python, with Python.

In programming, a static variable is the one allocated “statically,” which means its lifetime is throughout the program run.

Learn About Static Variables in C

Check out the changelog for staticvar here.

Installation

To get started, install staticvar by typing the following in your command line:

pip install staticvar

You can also manually download and install staticvar from PyPI.

Warning: staticvar only supports Python 3.10 and higher.

Usage

Importing

In your project, import the staticvar decorator as follows:

from staticvar import staticvar

Basics

Next, use the staticvar decorator above the target function and declare the name of the static variable along with its initial value as follows:

# Syntax: @staticvar("VARIABLE_NAME", INITIAL_VALUE)
@staticvar("foo", 0)
def bar():
	pass

Use the static variable by preceding it with the name of the function its used in:

@staticvar("foo", 0)
def bar():
	# Syntax: FUNCTION.VARIABLE_NAME
	bar.foo += 1

You can declare more than 1 static variable by stacking staticvar decorators at the top of the target function, and you can use all sorts of different data types:

@staticvar("eggs", 2.71828183)
@staticvar("spam", True)
def bar():
	if bar.spam is True:
		return bar.eggs

Typing

staticvar supports typing!... kind of... You can insert the type of the static variable as an argument in the decorator to ensure that the initial value is of the expected type, but staticvar cannot guarantee later values to be of the same type.

Here's how you can type-check your static variable with staticvar:

# Syntax: @staticvar("VARIABLE_NAME", INITIAL_VALUE, VARIABLE_TYPE)
@staticvar("foo", 0, int)
def bar():
	pass

Note: staticvar supports most types from Python's built-in typing module, but it cannot force type checking on complicated types. See edge cases.


If a variable of an unexpected type is passed, staticvar will raise a TypeError and terminate the program:

initialiser = 0.01

@staticvar("count", initialiser, int)
def bar():
	bar.count += 1

Output:

> TypeError: bar.count must be of type <class 'int'>. Current type: <class 'float'>

An example on how to utilise staticvar and static variables in a simple program

This is better done with Python's @cache decorator from functools, but staticvar can be used for memoization.

Here, we use staticvar to quickly print out the values of the Fibonacci sequence from 0 to 500. Normally, this program would take incredibly long to finish, but with staticvar, it will finish it in less than a second.

from staticvar import staticvar

@staticvar("cache", {0: 0, 1: 1}, dict)
def fibonacci(n: int) -> int:
	if n < 0:
		raise ValueError("n must be a non-negative integer.")

	# Checking if the value has already been computated before
	if n in fibonacci.cache:
		return fibonacci.cache[n]

	fibonacci.cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
	return fibonacci.cache[n]

for i in range(0, 501):
	print(fibonacci(i))

Console:

After the program runs, the values of the Fibonacci sequence from 0 to 500 get printed out instantaneously.

Configure

staticvar provides a class for editing its configurations.

Importing

To get started, import Configure into your project:

from staticvar import Configure

Suppressing and Unsuppressing Warnings

staticvar raises various warnings when possibly misused. If you know what you're doing though, you can suppress these warnings using the .suppress() method, as well as unsuppress them later using .unsuppress().

Here is the current list of warnings used by staticvar:

  • ComplicatedTypeWarning
  • UnpredictableBehaviourWarning

Just add the name of the warning you want to suppress as a string (you can write more than 1 in one go):

Configure.suppress('ComplicatedTypeWarning')

Better Errors

staticvar uses the stackprinter module to print more readable errors. Unfortunately, with how it's set up, all the errors raised with it are SystemExit errors. They are not caught by the convential Exception class. You can either catch BaseException or SystemExit or just disable this feature entirely to fallback to normal python exceptions.

You can write the following at the top of your file to raise the actual exceptions (and print the normal python traceback):

Configure.raise_better_errors(False)

Note: staticvar has some custom exceptions (e.g. UnsupportedTypeError). They can be imported and caught.

Edge Cases

Scope

staticvar doesn't create static variables exactly like the ones in C and C++, as that cannot be made with just Python. Instead, it assigns the variable name you specify to the target function as an attribute of it. This means that its scope is not within the function its defined in like C, but rather the scope of the function itself.

Take a look at this function to understand more (you don't need to focus on the details of it):

@staticvar("calls", 0)
def reverse_integer(n: int) -> int:
	reverse_interger.calls += 1 # Incrementing the number of times reverse_integer() is called

	sign = 1 if n >= 0 else -1 # Preserving the sign of the number
	n = abs(n) # Working with the absolute value of n

	reversed_number: int = 0
	while n != 0:
		n, digit = divmod(n, 10) # Getting the last digit and the rest of n
		reversed_number = reversed_number * 10 + digit 
	
	return sign * reversed_number

If we call this function, say, twice, then try to access the value of reverse_interger.calls, it will actually retrun 2:

print(reverse_integer(123)) # Output: 321
print(reverse_integer(2468)) # Output: 2468

print(reverse_interger.calls) # Output: 2, this would raise an error in C

Therefore, you don't need to put the static variable in the return of the function if you don't want to.

Lifetime

Similarly, the lifetime for staticvar variables is not always till the end of the program like in C. This is because of Python's support for nesting function definitions. The lifetime of the variable is actually just the lifetime of the function its declared in.

The following code should clear this up a little:

def main_function():
	@staticvar("count", 0, int)
	def nested_function():
		nested_function.count += 1
		return nested_function.count
	
	print(nested_function(), end=" ")
	print(nested_function(), end=" ")
	print(nested_function())

main_function()
'''
Output:
1 2 3
'''

main_function() # The nested function will then be redeclared, thus resetting the counter
'''
Output:
1 2 3

NOT 4 5 6
'''

staticvar provides a warning if used on nested functions since they basically defeat the purpose of using the static variable. If you know what you're doing, you can just do from staticvar import Configure and suppress the warning by writing this line at the top of your code:

Configure.suppress("UnpredictableBehaviourWarning")

"Complicated" Types

Most generic types like TypeVar or ParamSpec, detailed/parameterized generics like dict[str, int], and some other types don't work with staticvar. This is because staticvar does not assign the type specified to the static variable; it checks if it matches the the type of the initial value of it. With current Python's capibilities (and maybe mine, too), it's hard to account for each type without sacrificing some of the efficiency.

The following code:

T = TypeVar("T")
@staticvar("count", 0, T)
def my_function():
	pass

my_function()

should raise this error:

> UnsupportedTypeError: my_function.count: variable type must be a valid type or a generic type from the typing module. Type specified: ~T

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

staticvar-0.1.0.tar.gz (8.5 kB view details)

Uploaded Source

Built Distribution

staticvar-0.1.0-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file staticvar-0.1.0.tar.gz.

File metadata

  • Download URL: staticvar-0.1.0.tar.gz
  • Upload date:
  • Size: 8.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.5.1 CPython/3.11.4 Windows/10

File hashes

Hashes for staticvar-0.1.0.tar.gz
Algorithm Hash digest
SHA256 aae957806b5dc32e980b65a8bac6736e3f48352b2adb7592f531b83f94a3308e
MD5 436f775528e07d318740538b69405447
BLAKE2b-256 df0f82589b036496ba9ec993a69f781701680f777e356b807cb9f7b261d1be7d

See more details on using hashes here.

File details

Details for the file staticvar-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: staticvar-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.5.1 CPython/3.11.4 Windows/10

File hashes

Hashes for staticvar-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b8d68e89ae4855c08521a5a845cb22cc8f7cf99532b264f34142013a19ae1ce6
MD5 b739298531eefb9541c0854651ad5eef
BLAKE2b-256 77d235e24d8edd31c013e57c2cad450603e2ad824dbf7212a813d1612e0caf16

See more details on using hashes here.

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