Skip to content

Instantly share code, notes, and snippets.

@brahmlower
Last active September 18, 2016 22:29
Show Gist options
  • Save brahmlower/bfad5148507434e5525ed181d330c655 to your computer and use it in GitHub Desktop.
Save brahmlower/bfad5148507434e5525ed181d330c655 to your computer and use it in GitHub Desktop.
Dynamic property assignment with default custom get and set methods

This example implementation is extremely similar to what is being asked and answered on the following stack overflow page: http://stackoverflow.com/questions/1325673/how-to-add-property-to-a-python-class-dynamically

I have drawn a story to illustrate the resulting functionality of the AppleInterface, which works as though the attributes of Apple were it's own, and as if each of those attributes had been explicitly defined in the script.

There is an apple in a grass lawn in a small town. It has has a weight of 1 and is ripe. This apple is defined as follows

>>> apple_instance = Apple(1, True)

Our adventure starts when we wander outside our home and notice this apple. We will pick it up to investigate it. We are not an apple, so our interactions with the apple will be done through an interface.

>>> apple = AppleInterface(apple_instance)
===== Creating interface for Apple =====
Defining default get/set for attribute 'weight'.
Defining default get/set for attribute 'ripe'.
Attribute 'category' already exists.
========================================

With the apple interface, we can find the weight of the apple, and can see that it is ripe. However, we do not know what kind of apple it is.

>>> get_apple_weight(apple)
This apple weighs 1 units.
>>> get_apple_ripeness(apple)
This apple is ripe.
>>> get_apple_category(apple)
The type of apple is: unknown

We ask around town, and a local merchant claims that it is a "dragon apple". Though perposterous sounding, he is insistant, so we will try to set the category.

>>> set_apple_category(apple, "dragon apple")
Apple type of 'dragon apple' does not exist.

Attempting to set the value fails, because the category attribute of the AppleInterface has been manually defined to verify that the value is contained within a pre-set list of possible categories (we could simple observer AppleInterface.allowed_categories, but that defeats the purpose of this example).

We decide to go on a quest to identify this type of apple, as it is foreign to this land. We set out and after a 3 week journey, come across an elder with an auroa of wisdom about himself. He claims the apple is a Fuji apple, though it is no longer ripe after the long journey.

>>> apple.ripe = False
>>> set_apple_category(apple, "fuji")
The type of apple is: fuji

It is a Fuji apple, as the elder claimed.

In this journey we have read attributes of a class that were not manually defined, and were therefore defined with default get and set methods that would read and write values associated with an attribute of the same name from a "storage instance". That instance in this case was a simple class (Apple) that contained only attributes, but represents more complicated classes facilitating reading/writing data to more complicated storage mechanisms (databases for example).

In the case of the category attribute, we needed to execute some input validation before passing the provided value to storage. To facilitate this, we manually defined a get and set method in the AppleInterface class. Though the get method is the same as the otherwise default get method, the set method we defined will verify that the provided value by ensuring it is one of the strings in the class attribute list allowed_categories.

#!/usr/bin/python
# This example implementation is *extremely* similar to what is being asked and
# answered on the following stack overflow page:
# http://stackoverflow.com/questions/1325673/how-to-add-property-to-a-python-class-dynamically
# Simply running this script will return the following output, which follows
# a story told through inline comments.
# user@debian:~$ ./dynamic_property_test.py
# ===== Creating interface for Apple =====
# Defining default get/set for attribute 'weight'.
# Defining default get/set for attribute 'ripe'.
# Attribute 'category' already exists.
# ----------------------------------------
# This apple weighs 1 units.
# This apple is ripe.
# The type of apple is: unknown
# Apple type of 'dragon apple' does not exist.
# The type of apple is: fuji
class Apple(object):
"""Class to represent a storage object
These might be any sort of class that shouldn't be subclassed, but contains
most of the actual information you plan to have read and modified by the
subclasses of BaseTest.
"""
def __init__(self, weight = 0, ripe = False, category = 'unknown'):
""" These values undergo minimal value checking
The weight should be an integer, category a string, and ripe must be a
boolean. These values are not checked to ensure they are logical.
"""
self.weight = int(weight)
self.ripe = ripe
self.category = str(category)
class BaseTest(object):
fields = []
def __init__(self, storage_instance):
print "===== Creating interface for %s =====" % storage_instance.__class__.__name__
self.storage = storage_instance
self.define_properties()
print "-"*40
def define_properties(self):
for i in self.fields:
if not hasattr(self, i):
print "Defining default get/set for attribute '%s'." % i
attribute_get = self.__class__.dec_get(self, i)
attribute_set = self.__class__.dec_set(self, i)
prop = property(attribute_get, attribute_set)
setattr(self.__class__, i, prop)
else:
print "Attribute '%s' already exists." % i
def dec_get(self, attribute):
"""Property getter decorator
This will return a setter method for the property defined by the value
of attribute.
"""
def default_get(self):
return getattr(self.storage, attribute, None)
return default_get
def dec_set(self, attribute):
"""Property setter decorator
This will return a setter method for the property defined by the value
of attribute.
"""
def default_set(self, value):
setattr(self.storage, attribute, value)
return default_set
class AppleInterface(BaseTest):
fields = ["weight", "ripe", "category"]
allowed_categories = ["red delicious", "golden", "fuji"]
@property
def category(self):
return self.storage.category
@category.setter
def category(self, value):
if value not in self.allowed_categories:
raise ValueError
self.storage.category = value
# Before we start our adventure, we will define a couple short methods to give
# helpful output while setting/getting values from the AppleInterface class
def get_apple_weight(apple):
# Express the weight of an apple via an AppleInterface
print "This apple weighs %d units." % apple.weight
def get_apple_ripeness(apple):
# Express the ripeness of an apple via an AppleInterface
if apple.ripe:
print "This apple is ripe."
else:
print "This apple is not ripe."
def get_apple_category(apple):
# Express the category of an apple via an AppleInterface.
print "The type of apple is: %s" % apple.category
def set_apple_category(apple, category):
try:
apple.category = category
get_apple_category(apple)
except ValueError:
print "Apple type of '%s' does not exist." % category
# There is an apple in a grass lawn in a small town. It has has a weight of 1
# and is ripe. This apple is defined as follows
apple_instance = Apple(1, True)
# Our adventure starts when we wander outside our home and notice this apple.
# We pick it up, and can tell what the weight is, and can clearly tell that it
# is ripe, but we do not know what kind of apple it is.
apple = AppleInterface(apple_instance)
get_apple_weight(apple)
get_apple_ripeness(apple)
get_apple_category(apple)
# We ask around town, and the local merchant claims that it is a "dragon apple".
# Though perposterous sounding, he is insistant, so we'll try to set the
# category.
set_apple_category(apple, "dragon apple")
# Attempting to set the value fails with a value error. This is because the
# category attribute of the AppleInterface has been manually defined to allow
# for extra value checking.
# We decide to go on a quest to identify this type of apple, as it is foreign
# to this land. We set out and after a 3 week journey, come across an elder
# with an auroa of wisdom about himself. He claims the apple is a Fuji apple,
# though it is no longer ripe after the long journey.
apple.ripe = False
set_apple_category(apple, "fuji")
# It is indeed a fuji apple as the elder claimed. In this journey we have read
# attributes of a class that were not manually defined, and were therefore
# defined with default get and set methods, that would read and write values
# associated with an attribute of the same name from a "storage instance". That
# instance in this case was a simple class that contained only attributes, but
# represents more complicated classes facilitating reading/writing data to more
# complicated storage mechanisms (databases for example). In the case of the
# category attribute, we needed to execute some input validation before passing
# the provided value to storage. To facilitate this, we manually defined a
# get and set method in the AppleWrapper class. Though the get method is the
# same as the otherwise default get method, the set method we defined will
# verify that the provided value is valid, by ensuring it is one of the strings
# in the class attribute list 'allowed_categories'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment