Skip to main content

Infer properties from accessor methods.

Project description

Last release Python version Test status Test coverage GitHub last commit

Properties are a feature in python that allow accessor functions (i.e. getters and setters) to masquerade as regular attributes. This makes it possible to provide transparent APIs for classes that need to cache results, lazily load data, maintain invariants, or react in any other way to attribute access.

Unfortunately, making a property requires an annoying amount of boilerplate code. There are a few ways to do it, but the most common and most succinct requires you to decorate two functions (with two different decorators) and to type the name of the attribute three times:

>>> class RegularProperty:
...
...     @property
...     def attr(self):
...         print("getting attr")
...         return self._attr
...
...     @attr.setter
...     def attr(self, new_value):
...         print("setting attr")
...         self._attr = new_value
...
>>> obj = RegularProperty()
>>> obj.attr = 1
setting attr
>>> obj.attr
getting attr
1

The autoprop module simplifies this process by searching your class for accessor methods and adding properties corresponding to any such methods it finds. For example, the code below defines the same property as the code above:

>>> import autoprop
>>> @autoprop
... class AutoProperty:
...
...     def get_attr(self):
...         print("getting attr")
...         return self._attr
...
...     def set_attr(self, new_value):
...         print("setting attr")
...         self._attr = new_value
...
>>> obj = AutoProperty()
>>> obj.attr = 1
setting attr
>>> obj.attr
getting attr
1

Installation

Install autoprop using pip:

$ pip install autoprop

Usage

To use autoprop, import the autoprop module and use it directly as a class decorator:

>>> import autoprop
>>>
>>> @autoprop
... class Vector2D(object):
...
...     def __init__(self, x, y):
...         self._x = x
...         self._y = y
...
...     def get_x(self):
...         return self._x
...
...     def set_x(self, x):
...         self._x = x
...
...     def get_y(self):
...         return self._y
...
...     def set_y(self, y):
...         self._y = y
...
>>> v = Vector2D(1, 2)
>>> v.x, v.y
(1, 2)

The decorator searches your class for methods beginning with get_, set_, or del_ and uses them to create properties. The names of the properties are taken from whatever comes after the underscore. For example, the method get_x would be used to make a property called x. Any combination of getter, setter, and deleter methods is allowed for each property.

Caching

If you have properties that are expensive to calculate, it’s easy to cache them:

>>> @autoprop.cache
... class Simulation(object):
...
...     def get_data(self):
...         print("some expensive calculation...")
...         return 42
...
>>> s = Simulation()
>>> s.data
some expensive calculation...
42
>>> s.data
42

It’s also easy to cache some properties but not others:

>>> @autoprop.dynamic
... class Simulation(object):
...
...     def get_cheap(self):
...         print("some cheap calculation...")
...         return 16
...
...     @autoprop.cache
...     def get_expensive(self):
...         print("some expensive calculation...")
...         return 42
...
>>> s = Simulation()
>>> s.cheap
some cheap calculation...
16
>>> s.cheap
some cheap calculation...
16
>>> s.expensive
some expensive calculation...
42
>>> s.expensive
42

In order to enable caching for a class, you must decorate it with @autoprop.cache. This also sets the default caching behavior for any properties of that class. You can then override the default caching behavior for any specific getter method of that class by decorating it in the same way. Note that it is an error to use the @autoprop.cache decorator on non-getters, or in classes that have not enabled caching.

The @autoprop.cache() decorator accepts a policy keyword argument that determines when properties will be recalculated. The following policies are understood:

  • object: This is the default policy. Properties are recalculated when first accessed after a change to the object is detected. Changes are detected in three ways:

    1. One of the setter or deleter methods identified by autoprop is called. This includes if the method is indirectly called via a property.

    2. Any attribute of the object is set. This is detected by applying a decorator to the class’s __setattr__() implementation, or providing an implementation if one doesn’t exist. For classes that implement __setattr__() and __getattr__(), some care may be needed to avoid infinite recursion (because autoprop may cause these methods to be called earlier than you would normally expect).

    3. Any method decorated with @autoprop.refresh is called. This can be used to catch changes that would not otherwise be detected, e.g. changs to mutable objects.

  • class: Similar to object, but @autoprop.refresh will work even when applied to class methods and static methods. This is not the default because it adds some overhead and is not often necessary.

  • property: Properties are recalculated when first accessed after their own setter or deleter method has been called (whether directly or indirectly via a parameter). This is useful for properties that don’t depend on any other properties or object attributes.

  • dynamic: Properties are recalculated every time they are accessed. Note that @autoprop.dynamic is an alias for @autoprop.cache(policy='dynamic').

  • immutable: Properties are never recalculated, and are furthermore not allowed to have setter or deleter methods (an error will be raised if any such methods are found). As the name implies, this is for properties and classes that are intended to be immutable. Note that autoprop.immutable is an alias for @autoprop.cache(policy='immutable').

Details

Besides having the right prefix, there are two other criteria that methods must meet in order to be made into properties. The first is that they must take the right number of required arguments. Getters and deleters can’t have any required arguments (other than self). Setters must have exactly one required argument (other than self), which is the value to set. Default, variable, and keyword arguments are all ignored; only the number of required arguments matters.

Any methods that have the right name but the wrong arguments are silently ignored. This can be nice for getters that require, for example, an index. Even though such a getter can’t be made into a property, autoprop allows it to follow the same naming conventions as any getters that can be:

>>> @autoprop
... class Vector2D(Vector2D):
...
...     def get_coord(self, i):
...         if i == 0: return self.x
...         if i == 1: return self.y
...
...     def set_coord(self, i, new_coord):
...         if i == 0: self.x = new_coord
...         if i == 1: self.y = new_coord
...
>>> v = Vector2D(1, 2)
>>> v.get_x()
1
>>> v.get_coord(0)
1

In this way, users of your class can always expect to find accessors named get_* and set_*, and properties corresponding to those accessors for basic attributes that don’t need any extra information.

The second criterion is that the property must have a name which is not already in use. This guarantees that nothing you explicitly add to your class will be overwritten, and it gives you the ability to manually customize how certain properties are defined if you’d so like. This criterion does not apply to superclasses, so it is possible for properties to shadow attributes defined in parent classes.

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

autoprop-2.2.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

autoprop-2.2.0-py2.py3-none-any.whl (8.6 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file autoprop-2.2.0.tar.gz.

File metadata

  • Download URL: autoprop-2.2.0.tar.gz
  • Upload date:
  • Size: 13.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.10.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.7.10

File hashes

Hashes for autoprop-2.2.0.tar.gz
Algorithm Hash digest
SHA256 f6fd44b6690b8133cafb19dbeba6c2ace357d4aea2106cc8456c5798966dc6f8
MD5 53d9817271e21d97e22fba0fb26d0cfe
BLAKE2b-256 7aa915986cb376826bcc864349ca39d6a29a24912bd9de7fde4a6b5588bc9a15

See more details on using hashes here.

File details

Details for the file autoprop-2.2.0-py2.py3-none-any.whl.

File metadata

  • Download URL: autoprop-2.2.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 8.6 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/3.10.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.7.10

File hashes

Hashes for autoprop-2.2.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 d3bc45dd50f30a8520dee7c3c7a15a4edafaa06b714eeb653bca8eecb377f9f6
MD5 7a2f408339f17fe734dd6d8cd04b1041
BLAKE2b-256 cc2cce38554690be5e34ab16e542144bf55d98b6c2d97c7bd3bb784e178d6abb

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