Skip to main content

Constants without boilerplate

Project description

short-con: Constants without boilerplate

Motivation

When your Python code needs constants, the process often starts simply enough with the worthy goal of getting the magic strings and numbers out of your code.

KING = 'king'
QUEEN = 'queen'
ROOK = 'rook'
BISHOP = 'bishop'
KNIGHT = 'knight'
PAWN = 'pawn'

At some point, you might need to operate on those constants in groups, so you add some derived constants:

PIECES = (KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN)

We've hardly gotten out of the gate and the process already seems a bit tedious.

In addition, since none of those entities are very constant-like, clever Python programmers have created many ways to create constants beyond those simple approaches. Usually, the focus of those efforts is on ratcheting up immutability. But achieving truly constant values is somewhat beside the point in Python. The real annoyances have always more practical in nature.

Starting in Python 3.4, the enum library became available, and it helps a lot:

from enum import Enum

# A simple Enum with values from 1 through N.
Pieces = Enum('Pieces', 'KING QUEEN ROOK BISHOP KNIGHT PAWN')

# Or an Enum with custom values.
d = dict(KING = 'king', QUEEN = 'queen', ROOK = 'rook', BISHOP = 'bishop', KNIGHT = 'knight', PAWN = 'pawn')
Pieces = Enum('Pieces', d)

But even that solution is more than one usually wants. We started with the very simple goal of removing magic strings, numbers, and other simple values from the code and grouping those values in meaningful ways. But we ended up being forced to deal with an intermediate object that serves almost no purpose. Every time you want access to an underlying value, you have to dig down an extra level: for example, Pieces.QUEEN.value as opposed to just Pieces.QUEEN. The primitive value (an immutable string) is already constant-enough and robust-enough for the vast majority of use cases. Very little is gained by forcing coders to interact with an intermediate enum object that was never a goal to begin with.

An easier way

A better approach is to take inspiration from the excellent attrs library, which helps Python programmers create "classes without boilerplate". The short-con project provides a small convenience wrapper around attr.make_class to create handy vehicles for constants.

Attribute names can be supplied in the form of a list, tuple, or space-delimited string.

from short_con import constants

NAMES = 'KING QUEEN ROOK BISHOP KNIGHT PAWN'
xs = NAMES.split()

# All of these do the same thing.
Pieces = constants('Pieces', NAMES)
Pieces = constants('Pieces', xs)
Pieces = constants('Pieces', tuple(xs))

By default, constants() creates a frozen attrs-based class of the given name and returns an instance of it. That instance is immutable enough for most use cases:

Pieces.QUEEN = 'foobar'   # Fails with attrs.FrozenInstanceError.

The underlying values are directly accessible -- no need to interact with some bureaucratic object sitting in the middle:

assert Pieces.QUEEN == 'QUEEN'

The object is directly iterable and convertible to other collections:

for name, value in Pieces:
    print(name, value)

d = dict(Pieces)
tups = list(Pieces)

Various stylistic conventions are supported:

NAMES = 'KING QUEEN ROOK BISHOP KNIGHT PAWN'
names = NAMES.lower()

# Uppercase names, lowercase values.
Pieces = constants('Pieces', NAMES, value_style = 'lower')

# Or the reverse.
Pieces = constants('Pieces', names, value_style = 'upper')

# An enumeration from 1 through N.
Pieces = constants('Pieces', NAMES, value_style = 'enum')

Values can be declared explicitly in two ways:

# A dict.
d = dict(king = 0, queen = 9, rook = 5, bishop = 3, knight = 3, pawn = 1)
Pieces = constants('Pieces', d)

# A callable taking an INDEX and NAME and returning a VALUE.
f = lambda i, name: '{}-{}'.format(name.lower(), i + 1)
Pieces = constants('Pieces', NAMES, value_style = f)

Other customization of the attrs-based class can be passed through as well. The constants() function has the following signature, and the bases and attributes_arguments are passed through to attr.make_class.

def constants(name, attrs, value_style = None, bases = (object,), **attributes_arguments):
    ...

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

short-con-1.0.1.tar.gz (5.7 kB view details)

Uploaded Source

Built Distribution

short_con-1.0.1-py3-none-any.whl (5.5 kB view details)

Uploaded Python 3

File details

Details for the file short-con-1.0.1.tar.gz.

File metadata

  • Download URL: short-con-1.0.1.tar.gz
  • Upload date:
  • Size: 5.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.5

File hashes

Hashes for short-con-1.0.1.tar.gz
Algorithm Hash digest
SHA256 d3328343f334d9a0a6986c921f42c7594fb30974f5008fc8fca71a64c89ac709
MD5 f1c9bfeecf71bfceb0974f6d1f747d18
BLAKE2b-256 dfaea206dde5b12e8ad928d02329c6b9e85c6c79cee545dbb296fb33deb98d26

See more details on using hashes here.

File details

Details for the file short_con-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: short_con-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 5.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.5

File hashes

Hashes for short_con-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 42b6c90b8eb24aaedcfae244f2897018349406f6479aa7740f422db359953508
MD5 a7c2208b06de1f9cf8daa0fe7091aed2
BLAKE2b-256 99385635cab3b79f03bb47a679d24798bc100a99f8b4419540c5f5367f479da4

See more details on using hashes here.

Supported by

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