In PyMC3 you can do this
basic_model = pm.Model()
with basic_model:
# Priors for unknown model parameters
alpha = pm.Normal('alpha', mu=0, sd=10)
beta = pm.Normal('beta', mu=0, sd=10, shape=2)
sigma = pm.HalfNormal('sigma', sd=1)
# Expected value of outcome
mu = alpha + beta[0]*X1 + beta[1]*X2
# Likelihood (sampling distribution) of observations
Y_obs = pm.Normal('Y_obs', mu=mu, sd=sigma, observed=Y)
and all the variables (pm.Normal, ...) will be "assigned" to the basic_model instance.
From the docs
The first line,
basic_model = Model()
creates a new Model object which is a container for the model random
variables.
Following instantiation of the model, the subsequent specification of
the model components is performed inside a with statement:
with basic_model:
This creates a context manager, with our basic_model as the context,
that includes all statements until the indented block ends. This means
all PyMC3 objects introduced in the indented code block below the with
statement are added to the model behind the scenes. Absent this
context manager idiom, we would be forced to manually associate each
of the variables with basic_model right after we create them. If you
try to create a new random variable without a with model: statement,
it will raise an error since there is no obvious model for the
variable to be added to.
I think it is very elegant for the purpose of the library. How is that actually implemented?
The only way I can think of is something in the spirit of this:
class Model:
active_model = None
def __enter__(self):
Model.active_model = self
def __exit__(self, *args, **kwargs):
Model.active_model = None
class Normal:
def __init__(self):
if Model.active_model is None:
raise ValueError("cant instantiate variable outside of Model")
else:
self.model = Model.active_model
It works with my simple REPL tests but I am not sure if this have some pitfalls and is actually that simple.
You are very close, and it was even quite similar to your implementation for a while. Note that threading.local is used to store objects, and it is maintained as a list to allow nesting multiple models, and allows multiprocessing. There is a little extra in the actual implementation to allow setting theano configuration when entering a model context that I deleted:
class Context(object):
contexts = threading.local()
def __enter__(self):
type(self).get_contexts().append(self)
return self
def __exit__(self, typ, value, traceback):
type(self).get_contexts().pop()
#classmethod
def get_contexts(cls):
if not hasattr(cls.contexts, 'stack'):
cls.contexts.stack = []
return cls.contexts.stack
#classmethod
def get_context(cls):
"""Return the deepest context on the stack."""
try:
return cls.get_contexts()[-1]
except IndexError:
raise TypeError("No context on context stack")
The Model class subclasses Context, so when writing algorithms we can call Model.get_context() from inside a context manager and have access to the object. This is equivalent to your Model.active_model.
Related
In Tensorflow Federated (TFF), you can pass to the tff.learning.build_federated_averaging_process a broadcast_process and an aggregation_process, which can embed customized encoders e.g. to apply custom compressions.
Getting to the point of my question, I am trying to implement an encoder to sparsify model updates/model weights.
I am trying to build such an encoder by implementing the EncodingStageInterface, from tensorflow_model_optimization.python.core.internal.
However, I am struggling to implement a (local) state to accumulate the zeroed-out coordinates of model updates/model weights round by round. Note that this state should not be communicated, and just need to be maintained locally (so the AdaptiveEncodingStageInterface should not be helpful). In general, the question is how to maintain a local state inside an Encoder to be then passed to the fedavg process.
I attach the code of my encoder implementation (that, besides the state I would like to add, works fine as stateless as expected).
I then attach the excerpt of my code where I use the encoder implementation.
If I decomment the commented parts in stateful_encoding_stage_topk.py the code does not work: I can't figure out how manage the state (that is a Tensor) in TF non eager mode.
stateful_encoding_stage_topk.py
import tensorflow as tf
import numpy as np
from tensorflow_model_optimization.python.core.internal import tensor_encoding as te
#te.core.tf_style_encoding_stage
class StatefulTopKEncodingStage(te.core.EncodingStageInterface):
ENCODED_VALUES_KEY = 'stateful_topk_values'
INDICES_KEY = 'indices'
def __init__(self):
super().__init__()
# Here I would like to init my state
#self.A = tf.zeros([800], dtype=tf.float32)
#property
def name(self):
"""See base class."""
return 'stateful_topk'
#property
def compressible_tensors_keys(self):
"""See base class."""
return [self.ENCODED_VALUES_KEY]
#property
def commutes_with_sum(self):
"""See base class."""
return True
#property
def decode_needs_input_shape(self):
"""See base class."""
return True
def get_params(self):
"""See base class."""
return {}, {}
def encode(self, x, encode_params):
"""See base class."""
del encode_params # Unused.
dW = tf.reshape(x, [-1])
# Here I would like to retrieve the state
A = tf.zeros([800], dtype=tf.float32)
#A = self.residual
dW_and_A = tf.math.add(A, dW)
percentage = tf.constant(0.4, dtype=tf.float32)
k_float = tf.multiply(percentage, tf.cast(tf.size(dW), tf.float32))
k_int = tf.cast(tf.math.round(k_float), dtype=tf.int32)
values, indices = tf.math.top_k(tf.math.abs(dW_and_A), k = k_int, sorted = False)
indices = tf.expand_dims(indices, 1)
sparse_dW = tf.scatter_nd(indices, values, tf.shape(dW_and_A))
# Here I would like to update the state
A_updated = tf.math.subtract(dW_and_A, sparse_dW)
#self.A = A_updated
encoded_x = {self.ENCODED_VALUES_KEY: values,
self.INDICES_KEY: indices}
return encoded_x
def decode(self,
encoded_tensors,
decode_params,
num_summands=None,
shape=None):
"""See base class."""
del decode_params, num_summands # Unused.
indices = encoded_tensors[self.INDICES_KEY]
values = encoded_tensors[self.ENCODED_VALUES_KEY]
tensor = tf.fill([800], 0.0)
decoded_values = tf.tensor_scatter_nd_update(tensor, indices, values)
return tf.reshape(decoded_values, shape)
def sparse_quantizing_encoder():
encoder = te.core.EncoderComposer(
StatefulTopKEncodingStage() )
return encoder.make()
fedavg_with_sparsification.py
[...]
def sparsification_broadcast_encoder_fn(value):
spec = tf.TensorSpec(value.shape, value.dtype)
return te.encoders.as_simple_encoder(te.encoders.identity(), spec)
def sparsification_mean_encoder_fn(value):
spec = tf.TensorSpec(value.shape, value.dtype)
if value.shape.num_elements() == 800:
return te.encoders.as_gather_encoder(
stateful_encoding_stage_topk.sparse_quantizing_encoder(), spec)
else:
return te.encoders.as_gather_encoder(te.encoders.identity(), spec)
encoded_broadcast_process = (
tff.learning.framework.build_encoded_broadcast_process_from_model(
model_fn, sparsification_broadcast_encoder_fn))
encoded_mean_process = (
tff.learning.framework.build_encoded_mean_process_from_model(
model_fn, sparsification_mean_encoder_fn))
iterative_process = tff.learning.build_federated_averaging_process(
model_fn,
client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.004),
client_weight_fn=lambda _: tf.constant(1.0),
broadcast_process=encoded_broadcast_process,
aggregation_process=encoded_mean_process)
[...]
I am using:
tensorflow 2.4.0
tensorflow-federated 0.17.0
I'll try to answer in two parts; (1) top_k encoder without state and (2) realizing the stateful idea you seem to want in TFF.
(1)
To get the TopKEncodingStage working without state, I see a few details to change.
The commutes_with_sum property should be set to False. In pseudo-code, its meaning is whether sum_x(decode(encode(x))) == decode(sum_x(encode(x))) . This is not true for the representation your encode method returns -- summing the indices would not work well. I think implementation of the decode method can be simplified to
return tf.scatter_nd(
indices=encoded_tensors[self.INDICES_KEY],
updates=encoded_tensors[self.ENCODED_VALUES_KEY],
shape=shape)
(2)
What you refer to cannot be achieved in this manner using tff.learning.build_federated_averaging_process. The process returned by this method does not have any mechanism for maintaining client/local state. Whatever is the state expressed in your StatefulTopKEncodingStage would end up being the server state, not local state.
To work with the client/local state, you may need to write more custom code. For a starter, see examples/stateful_clients which you can adapt to store the state you refer to.
Keep in mind that in TFF, this will need to be represented as functional transformations. Storing values in attributes of a class and use them elsewhere can lead to surprising errors.
I defined the following Enum in Python:
class Unit(Enum):
GRAM = ("g")
KILOGRAM = ("kg", GRAM, 1000.0)
def __init__(self, symbol, base_unit = None, multiplier = 1.0):
self.symbol = symbol
self.multiplier = multiplier
self.base_unit = self if base_unit is None else base_unit
I would expect that
print(Unit.GRAM.base_unit)
print(Unit.KILOGRAM.base_unit)
will return
Unit.GRAM
Unit.GRAM
However, what I get is quite confusing
Unit.GRAM
g
Why is it so?
The way Python defines a class involves creating a new scope, processing a bunch of statements (variable assignments, function definitions, etc.), and then actually creating a class object based on the local variables which exist after all those statements have run. Nothing gets converted into Enum instances until that last step.
You could understand it somewhat like this:
def make_class_Unit():
GRAM = ("g")
KILOGRAM = ("kg", GRAM, 1000.0)
def __init__(self, symbol, base_unit = None, multiplier = 1.0):
self.symbol = symbol
self.multiplier = multiplier
self.base_unit = self if base_unit is None else base_unit
return make_class(name='Unit', base=Enum, contents=locals())
Unit = make_class_Unit()
Looking at it this way, hopefully you can tell that at the time when KILOGRAM is defined, GRAM is really just a string. It doesn't become a Unit instance until the last stage, where I call the (imaginary) make_class() function.1
1Even though the make_class function I used above doesn't actually exist under that name, it's not too different from what Python really does, which is calling the constructor of type or a metaclass (which in this case is the metaclass for Enums).
DavidZ explained the problem well.
The last bit that you need to solve this problem is this: when the __init__ of each member is being run, the Enum has been created -- so you can call it:
self.base_unit = self if base_unit is None else self.__class__(base_unit)
As I understand it, context managers are used in Python for defining initializing and finalizing pieces of code (__enter__ and __exit__) for an object.
However, in the tutorial for PyMC3 they show the following context manager example:
basic_model = pm.Model()
with basic_model:
# Priors for unknown model parameters
alpha = pm.Normal('alpha', mu=0, sd=10)
beta = pm.Normal('beta', mu=0, sd=10, shape=2)
sigma = pm.HalfNormal('sigma', sd=1)
# Expected value of outcome
mu = alpha + beta[0]*X1 + beta[1]*X2
# Likelihood (sampling distribution) of observations
Y_obs = pm.Normal('Y_obs', mu=mu, sd=sigma, observed=Y)
and mention that this has the purpose of associating the variables alpha, beta, sigma, mu and Y_obs to the model basic_model.
I would like to understand how such a mechanism works. In the explanations of context managers I have found, I did not see anything suggesting how variables or objects defined within the context's block get somehow "associated" to the context manager. It would seem that the library (PyMC3) somehow has access to the "current" context manager so it can associate each newly created statement to it behind the scenes. But how can the library get access to the context manager?
PyMC3 does this by maintaining a thread local variable as a class variable inside the Context class. Models inherit from Context.
Each time you call with on a model, the current model gets pushed onto the thread-specific context stack. The top of the stack thus always refers to the innermost (most recent) model used as a context manager.
Contexts (and thus Models) have a .get_context() class method to obtain the top of the context stack.
Distributions call Model.get_context() when they are created to associate themselves with the innermost model.
So in short:
with model pushes model onto the context stack. This means that inside of the with block, type(model).contexts or Model.contexts, or Context.contexts now contain model as its last (top-most) element.
Distribution.__init__() calls Model.get_context() (note capital M), which returns the top of the context stack. In our case this is model. The context stack is thread-local (there is one per thread), but it is not instance-specific. If there is only a single thread, there also is only a single context stack, regardless of the number of models.
When exiting the context manager. model gets popped from the context stack.
I don't know how it works in this specific case, but in general you will use some 'behind the scenes magic':
class Parent:
def __init__(self):
self.active_child = None
def ContextManager(self):
return Child(self)
def Attribute(self):
return self.active_child.Attribute()
class Child:
def __init__(self,parent):
self.parent = parent
def __enter__(self):
self.parent.active_child = self
def __exit__(self, exc_type, exc_val, exc_tb):
self.parent.active_child = None
def Attribute(self):
print("Called Attribute of child")
Using this code:
p = Parent()
with p.ContextManager():
attr = p.Attribute()
will yield to following output:
Called Attribute of child
One can also inspect the stack for locals() variables when entering and exiting the context manager block and identify which one have changed.
class VariablePostProcessor(object):
"""Context manager that applies a function to all newly defined variables in the context manager.
with VariablePostProcessor(print):
a = 1
b = 3
It uses the (name, id(obj)) of the variable & object to detect if a variable has been added.
If a name is already binded before the block to an object, it will detect the assignment to this name
in the context manager block only if the id of the object has changed.
a = 1
b = 2
with VariablePostProcessor(print):
a = 1
b = 3
# will only detect 'b' has newly defined variable/object. 'a' will not be detected as it points to the
# same object 1
"""
#staticmethod
def variables():
# get the locals 2 stack above
# (0 is this function, 1 is the __init__/__exit__ level, 2 is the context manager level)
return {(k, id(v)): v for k, v in inspect.stack()[2].frame.f_locals.items()}
def __init__(self, post_process):
self.post_process = post_process
# save the current stack
self.dct = self.variables()
def __enter__(self):
return
def __exit__(self, type, value, traceback):
# compare variables defined at __exist__ with variables defined at __enter__
dct_exit, dct_enter = self.variables(), self.dct
for (name, id_) in set(dct_exit).difference(dct_enter):
self.post_process(name, dct_exit[(name, id_)])
Typical use can be:
# let us define a Variable object that has a 'name' attribute that can be defined at initialisation time or later
class Variable:
def __init__(self, name=None):
self.name = name
# the following code
x = Variable('x')
y = Variable('y')
print(x.name, y.name)
# can be replaced by
with VariablePostProcessor(lambda name, obj: setattr(obj, "name", name)):
x = Variable()
y = Variable()
print(x.name, y.name)
# in such case, you can also define as a convenience
import functools
AutoRenamer = functools.partial(VariablePostProcessor, post_process=lambda name, obj: setattr(obj, "name", name))
# and rewrite the above code as
with AutoRenamer():
x = Variable()
y = Variable()
print(x.name, y.name) # => x y
I am currently designing a software which needs to manage a certain hardware setup.
The hardware setup is as following :
System - The system contains two identical devices, and has certain functionality relative to the entire system.
Device - Each device contains two identical sub devices, and has certain functionality relative to both sub devices.
Sub device - Each sub device has 4 configurable entities (Controlled via the same hardware command - thus I don't count them as a sub-sub device).
What I want to achieve :
I want to control all configurable entities via the system manager (the entities are counted in a serial way), meaning I would be able to do the following :
system_instance = system_manager_class(some_params)
system_instance.some_func(0) # configure device_manager[0].sub_device_manager[0].entity[0]
system_instance.some_func(5) # configure device_manager[0].sub_device_manager[1].entity[1]
system_instance.some_func(8) # configure device_manager[1].sub_device_manager[1].entity[0]
What I have thought of doing :
I was thinking of creating an abstract class, which contains all sub device functions (with a call to a conversion function) and have the system_manager, device_manager and sub_device_manager inherit it. Thus all classes will have the same function name and I will be able to access them via the system manager.
Something around these lines :
class abs_sub_device():
#staticmethod
def convert_entity(self):
sub_manager = None
sub_entity_num = None
pass
def set_entity_to_2(entity_num):
sub_manager, sub_manager_entity_num = self.convert_entity(entity_num)
sub_manager.some_func(sub_manager_entity_num)
class system_manager(abs_sub_device):
def __init__(self):
self.device_manager_list = [] # Initiliaze device list
self.device_manager_list.append(device_manager())
self.device_manager_list.append(device_manager())
def convert_entity(self, entity_num):
relevant_device_manager = self.device_manager_list[entity_num // 4]
relevant_entity = entity_num % 4
return relevant_device_manage, relevant_entity
class device_manager(abs_sub_device):
def __init__(self):
self.sub_device_manager_list = [] # Initiliaze sub device list
self.sub_device_manager_list.append(sub_device_manager())
self.sub_device_manager_list.append(sub_device_manager())
def convert_entity(self, entity_num):
relevant_sub_device_manager = self.sub_device_manager_list[entity_num // 4]
relevant_entity = entity_num % 4
return relevant_sub_device_manager, relevant_entity
class sub_device_manager(abs_sub_device):
def __init__(self):
self.entity_list = [0] * 4
def set_entity_to_2(self, entity_num):
self.entity_list[entity_num] = 2
The code is for generic understanding of my design, not for actual functionality.
The problem :
It seems to me that the system I am trying to design is really generic and that there must be a built-in python way to do this, or that my entire object oriented look at it is wrong.
I would really like to know if some one has a better way of doing this.
After much thinking, I think I found a pretty generic way to solve the issue, using a combination of decorators, inheritance and dynamic function creation.
The main idea is as following :
1) Each layer dynamically creates all sub layer relevant functions for it self (Inside the init function, using a decorator on the init function)
2) Each function created dynamically converts the entity value according to a convert function (which is a static function of the abs_container_class), and calls the lowers layer function with the same name (see make_convert_function_method).
3) This basically causes all sub layer function to be implemented on the higher level with zero code duplication.
def get_relevant_class_method_list(class_instance):
method_list = [func for func in dir(class_instance) if callable(getattr(class_instance, func)) and not func.startswith("__") and not func.startswith("_")]
return method_list
def make_convert_function_method(name):
def _method(self, entity_num, *args):
sub_manager, sub_manager_entity_num = self._convert_entity(entity_num)
function_to_call = getattr(sub_manager, name)
function_to_call(sub_manager_entity_num, *args)
return _method
def container_class_init_decorator(function_object):
def new_init_function(self, *args):
# Call the init function :
function_object(self, *args)
# Get all relevant methods (Of one sub class is enough)
method_list = get_relevant_class_method_list(self.container_list[0])
# Dynamically create all sub layer functions :
for method_name in method_list:
_method = make_convert_function_method(method_name)
setattr(type(self), method_name, _method)
return new_init_function
class abs_container_class():
#staticmethod
def _convert_entity(self):
sub_manager = None
sub_entity_num = None
pass
class system_manager(abs_container_class):
#container_class_init_decorator
def __init__(self):
self.device_manager_list = [] # Initiliaze device list
self.device_manager_list.append(device_manager())
self.device_manager_list.append(device_manager())
self.container_list = self.device_manager_list
def _convert_entity(self, entity_num):
relevant_device_manager = self.device_manager_list[entity_num // 4]
relevant_entity = entity_num % 4
return relevant_device_manager, relevant_entity
class device_manager(abs_container_class):
#container_class_init_decorator
def __init__(self):
self.sub_device_manager_list = [] # Initiliaze sub device list
self.sub_device_manager_list.append(sub_device_manager())
self.sub_device_manager_list.append(sub_device_manager())
self.container_list = self.sub_device_manager_list
def _convert_entity(self, entity_num):
relevant_sub_device_manager = self.sub_device_manager_list[entity_num // 4]
relevant_entity = entity_num % 4
return relevant_sub_device_manager, relevant_entity
class sub_device_manager():
def __init__(self):
self.entity_list = [0] * 4
def set_entity_to_value(self, entity_num, required_value):
self.entity_list[entity_num] = required_value
print("I set the entity to : {}".format(required_value))
# This is used for auto completion purposes (Using pep convention)
class auto_complete_class(system_manager, device_manager, sub_device_manager):
pass
system_instance = system_manager() # type: auto_complete_class
system_instance.set_entity_to_value(0, 3)
There is still a little issue with this solution, auto-completion would not work since the highest level class has almost no static implemented function.
In order to solve this I cheated a bit, I created an empty class which inherited from all layers and stated to the IDE using pep convention that it is the type of the instance being created (# type: auto_complete_class).
Does this solve your Problem?
class EndDevice:
def __init__(self, entities_num):
self.entities = list(range(entities_num))
#property
def count_entities(self):
return len(self.entities)
def get_entity(self, i):
return str(i)
class Device:
def __init__(self, sub_devices):
self.sub_devices = sub_devices
#property
def count_entities(self):
return sum(sd.count_entities for sd in self.sub_devices)
def get_entity(self, i):
c = 0
for index, sd in enumerate(self.sub_devices):
if c <= i < sd.count_entities + c:
return str(index) + " " + sd.get_entity(i - c)
c += sd.count_entities
raise IndexError(i)
SystemManager = Device # Are the exact same. This also means you can stack that infinite
sub_devices1 = [EndDevice(4) for _ in range(2)]
sub_devices2 = [EndDevice(4) for _ in range(2)]
system_manager = SystemManager([Device(sub_devices1), Device(sub_devices2)])
print(system_manager.get_entity(0))
print(system_manager.get_entity(5))
print(system_manager.get_entity(15))
I can't think of a better way to do this than OOP, but inheritance will only give you one set of low-level functions for the system manager, so it wil be like having one device manager and one sub-device manager. A better thing to do will be, a bit like tkinter widgets, to have one system manager and initialise all the other managers like children in a tree, so:
system = SystemManager()
device1 = DeviceManager(system)
subDevice1 = SubDeviceManager(device1)
device2 = DeviceManager(system)
subDevice2 = SubDeviceManager(device2)
#to execute some_func on subDevice1
system.some_func(0, 0, *someParams)
We can do this by keeping a list of 'children' of the higher-level managers and having functions which reference the children.
class SystemManager:
def __init__(self):
self.children = []
def some_func(self, child, *params):
self.children[child].some_func(*params)
class DeviceManager:
def __init__(self, parent):
parent.children.append(self)
self.children = []
def some_func(self, child, *params):
self.children[child].some_func(*params)
class SubDeviceManager:
def __init__(self, parent):
parent.children.append(self)
#this may or may not have sub-objects, if it does we need to make it its own children list.
def some_func(self, *params):
#do some important stuff
Unfortunately, this does mean that if we want to call a function of a sub-device manager from the system manager without having lots of dots, we will have to define it again again in the system manager. What you can do instead is use the built-in exec() function, which will take in a string input and run it using the Python interpreter:
class SystemManager:
...
def execute(self, child, function, *args):
exec("self.children[child]."+function+"(*args)")
(and keep the device manager the same)
You would then write in the main program:
system.execute(0, "some_func", 0, *someArgs)
Which would call
device1.some_func(0, someArgs)
Here's what I'm thinking:
SystemManager().apply_to_entity(entity_num=7, lambda e: e.value = 2)
class EntitySuperManagerMixin():
"""Mixin to handle logic for managing entity managers."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # Supports any kind of __init__ call.
self._entity_manager_list = []
def apply_to_entity(self, entity_num, action):
relevant_entity_manager = self._entity_manager_list[index // 4]
relevant_entity_num = index % 4
return relevant_entity_manager.apply_to_entity(
relevant_entity_num, action)
class SystemManager(EntitySuperManagerMixin):
def __init__(self):
super().__init__()
# An alias for _entity_manager_list to improve readability.
self.device_manager_list = self._entity_manager_list
self.device_manager_list.extend(DeviceManager() for _ in range(4))
class DeviceManager(EntitySuperManagerMixin):
def __init__(self):
super().__init__()
# An alias for _entity_manager_list to improve readability.
self.sub_device_manager_list = self._entity_manager_list
self.sub_device_manager_list.extend(SubDeviceManager() for _ in range(4))
class SubDeviceManager():
"""Manages entities, not entity managers, thus doesn't inherit the mixin."""
def __init__(self):
# Entities need to be classes for this idea to work.
self._entity_list = [Entity() for _ in range(4)]
def apply_to_entity(self, entity_num, action):
return action(self._entity_list[entity_num])
class Entity():
def __init__(self, initial_value=0):
self.value = initial_value
With this structure:
Entity-specific functions can stay bound to the Entity class (where it belongs).
Manager-specific code needs to be updated in two places: EntitySuperManagerMixin and the lowest level manager (which would need custom behavior anyway since it deals with the actual entities, not other managers).
The way i see it if you want to dynamically configure different part of system you need some sort of addressing so if you input an ID or address with some parameter the system will know with address on which sub sistem you are talking about and then configure that system with parameter.
OOP is quite ok for that and then you can easily manipulate such data via bitwise operators.
So basic addressing is done via binary system , so to do that in python you need first to implement an address static attribute to your class with perhaps some basic further detailing if system grows.
Basic implementation of addres systems is as follows:
bin(71)
1010 1011
and if we divide it into nibbles
1010 - device manager 10
1011 - sub device manager 11
So in this example we have system of 15 device managers and 15 sub device menagers, and every device and sub device manager has its integer address.So let's say you want to access device manager no10 with sub device manager no11. You would need their address which is in binary 71 and you would go with:
system.config(address, parameter )
Where system.config funcion would look like this:
def config(self,address, parameter):
device_manager = (address&0xF0)>>4 #10
sub_device_manager = address&0xf # 11
if device_manager not in range(self.devices): raise LookupError("device manager not found")
if sub_device_manager not in range(self.devices[device_manager].device): raise LookupError("sub device manager not found")
self.devices[device_manager].device[sub_device_manager].implement(parameter)
In layman you would tell system that sub_device 11 from device 10 needs configuration with this parameter.
So how would this setup look in python inheritance class of some base class of system that could be then composited/inherited to different classes:
class systems(object):
parent = None #global parent element, defaults to None well for simplicity
def __init__(self):
self.addrMASK = 0xf # address mask for that nibble
self.addr = 0x1 # default address of that element
self.devices = [] # list of instances of device
self.data = { #some arbitrary data
"param1":"param_val",
"param2":"param_val",
"param3":"param_val",
}
def addSubSystem(self,sub_system): # connects elements to eachother
# checks for valiability
if not isinstance(sub_system,systems):
raise TypeError("defined input is not a system type") # to prevent passing an integer or something
# appends a device to system data
self.devices.append(sub_system)
# search parent variables from sub device manager to system
obj = self
while 1:
if obj.parent is not None:
obj.parent.addrMASK<<=4 #bitshifts 4 bits
obj.parent.addr <<=4 #bitshifts 4 bits
obj = obj.parent
else:break
#self management , i am lazy guy so i added this part so i wouldn't have to reset addresses manualy
self.addrMASK <<=4 #bitshifts 4 bits
self.addr <<=4 #bitshifts 4 bits
# this element is added so the obj address is coresponding to place in list, this could be done more eloquently but i didn't know what are your limitations
if not self.devices:
self.devices[ len(self.devices)-1 ].addr +=1
self.devices[ len(self.devices)-1 ].parent = self
# helpful for checking data ... gives the address of system
def __repr__(self):
return "system at {0:X}, {1:0X}".format(self.addr,self.addrMASK)
# extra helpful lists data as well
def __str__(self):
data = [ '{} : {}\n'.format(k,v) for k,v in self.data.items() ]
return " ".join([ repr(self),'\n',*data ])
#checking for data, skips looping over sub systems
def __contains__(self,system_index):
return system_index-1 in range(len(self.data))
# applying parameter change -- just an example
def apply(self,par_dict):
if not isinstance(par_dict,dict):
raise TypeError("parameter must be a dict type")
if any( key in self.data.keys() for key in par_dict.keys() ):
for k,v in par_dict.items():
if k in self.data.keys():
self.data[k]=v
else:pass
else:pass
# implementing parameters trough addresses
def implement(self,address,parameter_dictionary):
if address&self.addrMASK==self.addr:
if address-self.addr!=0:
item = (address-self.addr)>>4
self.devices[item-1].implement( address-self.addr,parameter_dictionary )
else:
self.apply(parameter_dictionary)
a = systems()
b = systems()
a.addSubSystem(b)
c = systems()
b.addSubSystem(c)
print('a')
print(a)
print('')
print('b')
print(b)
print('')
print('c')
print(c)
print('')
a.implement(0x100,{"param1":"a"})
a.implement(0x110,{"param1":"b"})
a.implement(0x111,{"param1":"c"})
print('a')
print(a)
print('')
print('b')
print(b)
print('')
print('c')
print(c)
print('')
I am creating a simple game that contains classes called 'Player' and 'Strategy'. I want to assign a Strategy instance to the Player instance when the Player is created.
class Player(object):
def __init__(self):
self.Strategy = None
def Decision(self, InputA, InputB):
Result = self.Strategy(InputA, InputB)
return Result
def SetStrategy(self):
# Sets a strategy instance to the Player instance
class Strategy(object):
def Strategy1(self, InputA, InputB):
return InputA * InputB
def Strategy2(self, InputA, InputB):
return (InputA - InputB) / 2
def Strategy3(self, InputA, InputB):
return 0
What I'm trying to achieve:
in[0] Player1 = Player()
in[1] Player2 = Player()
in[2]: Player1.SetStrategy('Strategy1')
in[3]: Player2.SetStrategy('Strategy3')
in[4]: Player1.Decision(2,5)
out[0]: 10
in[5]: Player2.Decision(3,6)
out[1]: 0
Searching here and via google shows me ways of doing it with monkey patching but the approach looks a little inelegant (and although I'm a beginner I think there's a better way to do it) - is there a way to do this with inheritance that I'm not seeing?
def strategy1(inputA, inputB): # 2
return inputA * inputB
def strategy2(inputA, inputB):
return (inputA - inputB) / 2
def strategy3(inputA, inputB):
return 0
strategy = {
'mul': strategy1,
'diff': strategy2,
'zero': strategy3
}
class Player(object):
def __init__(self, strategy_name='mul'): # 1
self.strategy_name = strategy_name # 5
def decision(self, inputA, inputB): # 4
result = strategy[self.strategy_name](inputA, inputB)
return result
player1 = Player()
player2 = Player()
player1.strategy_name = 'mul' # 3
player2.strategy_name = 'zero'
print(player1.decision(2, 5))
# 10
print(player2.decision(3, 6))
# 0
Every player has a strategy, so don't allow instantiation of Player
without assigning some strategy. You could use a default strategy
(as shown below), or make strategy a mandatory argument.
The strategies could be plain functions; I don't see a reason to
bundle them as methods of a Strategy class. Always keep code as
simple as possible; don't use a class when a function would suffice;
use a class when it provides some feature (such as inheritance) which
makes the class-based solution simpler.
In Python there is no need for getters/setters like setStrategy.
You can use plain attributes for simple values, and properties to
implement more complicated behavior. Attributes and properties use
the same syntax, so you can switch from one to the other without
having to change have the class is used.
There is a convention (recommended in PEP8) that classes be named in
CamelCase, and instances, functions and variables in lowercase. The
convention is used ubiquitously, and following it will help other
understand your code more easily.
To make it easy to store the strategy in a database, you could store
the strategy_name in the database, and use a lookup dict (such as
strategy) to associate the name with the actual function.