Observer pattern updating after unsubscribing - python

I am currently trying to implement the observer design pattern on a List class. The list class is the subject, and a calculate class is the observer. The calculate class needs to observe the list class, and calculate the sum of the values in the list in the list class. However, when the observer unsubscribes from the List, it is still getting the updates. And if the observer subscribes to a new list, the list before unsubscribing is still showing. Does anyone have any tips to why this happens?
from abc import ABCMeta, abstractmethod
class Observer(metaclass = ABCMeta):
#abstractmethod
def update(self, data):
pass
class Subject(metaclass = ABCMeta):
#abstractmethod
def subscribe(self, observer):
pass
#abstractmethod
def unsubscribe(self, observer):
pass
#abstractmethod
def notify(self):
pass
class List(Subject):
def __init__(self):
self.__list = []
self._observers = set()
def add(self, data):
self.__list.append(data)
self.notify()
def subscribe(self, observer):
self._observers.add(observer)
def unsubscribe(self, observer):
self._observers.discard(observer)
def notify(self):
for obs in self._observers:
obs.update(self.__list)
class Calculate(Observer):
def __init__(self, lst):
self.__data = []
self._list = lst
self._list.subscribe(self)
def update(self, data):
self.__data = data
def calculate(self):
total = 0
for item in self.__data:
total += item
return total
def remove(self):
self._list.unsubscribe(self)
def attach(self, lst):
self._list.subscribe(self)
So, this is my test and the output i get:
first_list = List()
first_list.add(1)
list_observer = Calculate(first_list)
first_list.add(5)
list_observer.calculate()
This returns 6, which is correct. However, when i do this:
list_observer.remove()
first_list.add(5)
list_observer.calculate()
I get 11. Which in sense, is the correct calculation, but since the observer unsubscribed, why is it still getting the updates? I also tried subscribing to a new list, but the old data is still in there.

This has to do with the type of data you pass. The list of data is mutable. I can't explain it that well, but this answer is what you are looking for.
How do I pass a variable by reference?
You can fix it by doing this, you take a copy of the original object:
def update(self, data):
self.__data = data.copy()

Related

Inheritance of OptaPy planning_entitiy classes?

I'm working on a schedule optimization solution with Optapy.
I'm trying like to make a chained planned variable, which should include multiple types of actual entities in the chain. Something like this:
#planning_entity
class Slot:
""" Schedule slot """
def __init__(self):
self.prev_slot = None
#planning_variable(Slot, value_range_provider_refs=['slot_range'],
graph_type=PlanningVariableGraphType.CHAINED)
def get_previous_slot(self):
return self.prev_slot
def set_previous_slot(self, previous_slot: 'Slot'):
self.prev_slot = previous_slot
#planning_entity
class Task(Slot):
""" Allocation to task """
...
#planning_entity
class LunchBreak(Slot):
""" Lunch break """
...
This doesn't work by various reasons. If I don't add any planning variable to derived classes, it fails saying I have to, but if I add them - I'm getting java exceptions like 'assignment error' or similar.
Is it actually possible to inherit one #planning_entity class from another in Optapy?
The issue is in this code:
#planning_entity
class Slot:
""" Schedule slot """
def __init__(self):
self.prev_slot = None
#planning_variable(Slot, value_range_provider_refs=['slot_range'],
graph_type=PlanningVariableGraphType.CHAINED)
def get_previous_slot(self):
return self.prev_slot
def set_previous_slot(self, previous_slot: 'Slot'):
self.prev_slot = previous_slot
Due to Python semantics, the class Slot is not available during it own definition. However, chained models / subclasses are possible in optapy. There is a bug in the current version of optapy (https://github.com/optapy/optapy/issues/101) that throws a ClassCastException if the #value_range_provider type does not EXACTLY match the #planning_variable type. This can be work around by using the most derived super class (which in this case, is Slot) in the #planning_solution. Additionally, there seem to be a problem with multiple #planning_entity classes in chained models that I'll investigate. To accomplish having multiple subclasses of planning entities (without new #planning_variables), the following code will work:
import optapy
from optapy.score import HardSoftScore
from optapy.types import PlanningVariableGraphType
#optapy.problem_fact
class Base:
pass
#optapy.planning_entity
class Slot(Base):
def __init__(self, value=None):
self.value = value
#optapy.planning_variable(Base, value_range_provider_refs=['employee_range', 'task_range', 'lunch_break_range'],
graph_type=PlanningVariableGraphType.CHAINED)
def get_value(self):
return self.value
def set_value(self, value):
self.value = value
#optapy.problem_fact
class Employee(Base): # chained models need an anchor
def __init__(self, code):
self.code = code
#optapy.planning_entity
class Task(Slot):
def __init__(self, code, value=None):
self.code = code
self.value = value
#optapy.planning_entity
class LunchBreak(Slot):
def __init__(self, code, value=None):
self.code = code
self.value = value
#optapy.planning_solution
class Solution:
def __init__(self, employees, tasks, lunch_breaks, score=None):
self.employees = employees
self.tasks = tasks
self.lunch_breaks = lunch_breaks
self.score = score
#optapy.problem_fact_collection_property(Slot)
#optapy.value_range_provider('employee_range')
def get_employees(self):
return self.employees
#optapy.planning_entity_collection_property(Slot)
#optapy.value_range_provider('task_range')
def get_tasks(self):
return self.tasks
#optapy.planning_entity_collection_property(Slot)
#optapy.value_range_provider('lunch_break_range')
def get_lunch_breaks(self):
return self.lunch_breaks
#optapy.planning_score(HardSoftScore)
def get_score(self):
return self.score
def set_score(self, score):
self.score = score
def build_problem():
employees = [
Employee('Amy'),
Employee('Beth')
]
tasks = [
Task('T1'),
Task('T2'),
Task('T3')
]
lunch_breaks = [
LunchBreak('L1'),
LunchBreak('L2')
]
return Solution(employees, tasks, lunch_breaks)
And to create the solver:
import optapy
import optapy.config
from optapy.types import Duration
from domain import Solution, Slot, build_problem
from constraints import define_constraints
solver_config = (optapy.config.solver.SolverConfig()
.withSolutionClass(Solution)
.withEntityClasses(Slot)
.withConstraintProviderClass(define_constraints)
.withTerminationSpentLimit(Duration.ofSeconds(10))
)
solver_factory = optapy.solver_factory_create(solver_config)
solver = solver_factory.buildSolver()
solver.solve(build_problem())

Using property to return a specific list entry

Hy,
I have a small class with only one attribute, which is a list with four elements. I want to make attributes for each object of the list with the help of property, but I don't want to write a setter and a getter method for each element. Currently I implemented it in the following way.
class MyClass:
def __init__(self):
self.my_list = [None in range(4)]
def __get_my_list_x(self, x):
return self.my_list[x]
def __set_my_list_x(self, val, x):
self.my_list[x] = val
def get_my_list_0(self):
return self.__get_my_list_x(x=0)
def set_my_list_0(self, val):
self.__set_my_list_x(val, x=0)
# continue getter and setter methods for position 1, 2 and 3
# of my list
my_list_0 = property(get_my_list_0, set_my_list_0)
my_list_1 = property(get_my_list_1, set_my_list_1)
my_list_2 = property(get_my_list_2, set_my_list_2)
my_list_3 = property(get_my_list_3, set_my_list_3)
At the moment I'm violating the Don't repeat yourself principle, because I have to write the getter and setter methods for my_list_0 to my_list_3. Is there a way to directly call the methods __get_my_list_x and __set_my_list_x in property() and specify the x argument?
I hope you guys get my question.
Have a nice day.
There are a lot of different solutions possible depending on your exact situation outside of this probably oversimplified example.
The best solution if you need to use actual attributes is probably to define your own custom descriptors (e.g. what property does under the hood):
class MyListIndexer:
def __init__(self, index):
self.index = index
def __get__(self, instance, owner):
return instance.my_list[self.index]
def __set__(self, instance, value):
instance.my_list[self.index] = value
class MyClass:
def __init__(self):
self.my_list = [None for _ in range(4)]
my_list_0 = MyListIndexer(0)
my_list_1 = MyListIndexer(1)
You can also add another parameter to MyListIndexer specifying the name of the attribute with help of getattr.
However, consider not using attributes at all and instead providing something like direct item access with __getitem__/__setitem__:
class MyClass:
def __init__(self):
self.my_list = [None for _ in range(4)]
def __setitem__(self, key, value):
self.my_list[key] = value
def __getitem__(self, item):
return self.my_list[item]
The extreme general solution that might have unexpected consequences and should only be used if there is no other solution is to use the __getattr__/__setattr__ functions:
class MyClass:
def __init__(self):
self.my_list = [None for _ in range(4)]
def __getattr__(self, item):
if item.startswith("my_list_"):
val = int(item[8:])
return self.my_list[val]
else:
return super(MyClass, self).__getattr__(item)
def __setattr__(self, key, value):
if key.startswith("my_list_"):
ind = int(key[8:])
self.my_list[ind] = value
else:
super(MyClass, self).__setattr__(key, value)

Referencing other objects of same class in a method of a class in Python

I am new to programming and learning Python. Right now I am trying to figure out howto write a value to another object of the same class inside of a method definition inside the class. I mean something like this:
class myClass:
def __init__(self, attribute, lst = True):
self.attribute = attribute
if lst is True:
self.lst = []
def add_amount(self, amount):
self.lst.append(dict(Amount = amount))
def total:
self.total = []
for each in self.lst:
self.total.append(each.get("Amount"))
self.total = sum(self.total)
#def transfer(self, amount, attribute):
#here I would like to be able to add
#a value (for example self.total) to
#the lst [] of a different instance of
#this class
firstInstance = myClass("First")
secondInstance = myClass("Second")
firstInstance.add_amount(10)
firstInstance.add_amount(20)
##firstInstance.transfer(15, "Second")
#How can I write the transfer
#function so that it will add
#the value of the first argument
#to the empty list of the instance
#object with the attribute "Second"
#(in this case secondInstance)?
How do I have to program that? I hope I explained my problem in an understandable way, hope you guys can help me! Thanks in advance! :)
Kind regards
You would have to pass the secondInstance as an argument to your transfer function. You could potentially do something like this:
class myClass:
def __init__(self, attribute, lst = True):
self.attribute = attribute
if lst is True:
self.lst = []
def add_amount(self, amount):
self.lst.append(dict(Amount = amount))
def subract_amount(self, amount):
# TODO
pass
def total:
self.total = []
for each in self.lst:
self.total.append(each.get("Amount"))
self.total = sum(self.total)
def transfer(self, amount, other):
self.subtract_amount(amount)
other.add_amount(amount)
firstInstance = myClass("First")
secondInstance = myClass("Second")
firstInstance.add_amount(10)
firstInstance.add_amount(20)
firstInstance.transfer(15, secondInstance)

Overwrite all class methods with dummy

I have a class which has its own methods, for example:
class Original():
def __init__(self, dummy=False):
self.dummy = dummy
def funcA(self):
print('funcA')
And I want that, in case the variable dummy is true, all the custom made functions from class Original (e.g., funcA) become dummy (i.e., don't do nothing and return nothing).
I have managed to do a dummy class like this:
class Dummy(object):
def dummy(*args, **kwargs):
pass
def __getattr__(self, _):
return self.dummy
a = Dummy()
a.asd() # returns nothing
However, I can't manage to make a class in which the writen functions work in case the variable dummy is False, and they don't if the variable is True.
Any help please?
Managed to figure it out based on Alex Hall's comment. Hope this helps anyone out there:
class Dummy(object):
def __init__(self, isDummy):
self.isDummy = isDummy
def dummy(*args, **kwargs):
pass
def __getattribute__(self, item):
if item in ['isDummy', 'dummy'] or self.isDummy is False:
attr = object.__getattribute__(self, item)
return attr
else:
return self.dummy
def funcA(self):
print('funcA')
print('Dummy:')
dummy = Dummy(isDummy=True)
dummy.funcA() # returns nothing
print('---')
print('nonDummy:')
nonDummy = Dummy(isDummy=False)
nonDummy.funcA() # prints 'funcA'

Derived class with differing interface

The following code:
class Cache:
def __init__(self):
self._cache = []
def store(self, data):
self._cache.append(data)
def stats(self):
print('We are caching {} elements'.format(len(self._cache)))
class LegoCache(Cache):
def store(self, weight, color):
Cache.store(self, (weight, color))
has a problem in that the store method does not implement the interface of the base class.
How can this code be improved? I have the following ideas:
do not derive from Cache, just make use of it.
rename the store method in the base class to store_base
Are there other alternatives?
EDIT
The base class must also support this other use case:
class ZombieCache(Cache):
def store(self, dead_since, humans_eaten, can_be_saved=False):
Cache.store(self, dict(
dead_since=dead_since,
humans_eaten=humans_eaten,
can_be_saved=can_be_saved))
You may use a variable argument list in the base class:
class Cache:
def __init__(self):
self._cache = []
def store(self, *args):
self._cache.append(args)
def stats(self):
print('We are caching {} elements'.format(len(self._cache)))
class LegoCache(Cache):
pass
# "overloading" store isn't needed
So it isn't needed to overload this method or add methods with different names for specials cases:
cache = Cache()
legoCache = LegoCache()
cache.store(x)
legoCache.store(x, y)
Another solution may be delegation:
class LegoCache(object):
def __init__(self):
self.cache = Cache()
def store(self, weight, color):
self.cache.store((weight, color))
# or just self.cache.store(weight, color) if you use the *args implementation
I would implement it like this,
class Cache:
def __init__(self):
self._cache = []
def store(self, data):
self._cache.append(data)
def stats(self):
print('We are caching {} elements'.format(len(self._cache)))
class LegoData(object):
def __init__(self, weight, color):
self.weight = weight
self.color = color
class LegoCache(Cache):
pass
Client will access it like this,
lego_cache = LegoCache()
lego_cache.store(LegoData(weight=10, color='Green'))

Categories

Resources