Python: Method in subclasses that has different assignments - python

I have a method run() in subclasses, which has an API POST request in each subclass, gets data from the POST request, and assigns an ID from this data to self._id. Now I would like to get a description too. However, description is returned only in the API request self._api_obj.trigger(...) in SubClassB.run, not in the API request self._api_obj.trigger_run(...) in SubClassA.run. For SubclassA I need a separate API request to get the description.
I tried the following, but I don't think it's a good idea to assign 2 attributes in SubClassB.run, but only 1 attribute in SubClassA.run. Right? Since from my understanding, the same method in subclasses should have the same behavior (Just different implementation).
class SuperClass:
def __init__(self):
self._id = None # Assigned in run()
self._description = None
#property
def description(self):
raise NotImplementedError
def run(self, *args):
raise NotImplementedError
#property
def id(self):
return self._id
class SubClassA(SuperClass):
def __init__(self):
super().__init__()
self._api_obj = ApiObj1()
#property
def description(self):
if not self._description:
_result = self._api_obj.get_data()
self._description = _result["description"]
return self._description
def run(self, *args):
_result = self._api_obj.trigger_run(foo="foo")
self._id = _result["RunId"]
class SubClassB(SuperClass):
def __init__(self):
super().__init__()
self._api_obj = ApiObj2()
#property
def description(self):
return self._description
def run(self, *args):
_result = self._api_obj.trigger(foo="foo", bar="bar", arg1="arg1", arg2="arg2")
self._id = _result["data"]["id"]
self._description = _result["data"]["description"]
Is there a better way to add assignment to self._description? Or some other solution to include description?

Here is a solution I thought of: Creating a private method _run(), that will set and return the complete result of the run. The method SubClassB.get_description() will be able to get any required info from it. However, SubClassA.get_description() will get the description from the API.
class SuperClass:
def __init__(self):
self._id = None # Assigned in run()
self._run_result_info = None # Assigned in _run()
def _run(self, *args):
raise NotImplementedError
def run(self):
raise NotImplementedError
def get_description(self):
raise NotImplementedError
#property
def id(self):
return self._id
class SubClassA(SuperClass):
def __init__(self):
super().__init__()
self._api_obj = ApiObj1()
def _run(self):
self._run_result_info = self._api_obj.trigger_run(foo="foo")
return self._run_result_info
def run(self):
_result = self._run()
self._id = _result["RunId"]
def get_description(self):
_result = self._api_obj.get_data()
_description = _result["description"]
return _description
class SubClassB(SuperClass):
def __init__(self):
super().__init__()
self._api_obj = ApiObj2()
def _run(self):
self._run_result_info = self._api_obj.trigger(foo="foo", bar="bar", arg1="arg1", arg2="arg2")
return self._run_result_info
def run(self):
_result = self._run()
self._id = _result["data"]["id"]
def get_description(self):
return self._run_result_info["data"]["description"]
Another option: Not keeping the result of the run, since it's not always needed. Just have a property description that will get the description through an API request only in cases it's needed. It means that we will need an API request in SubClassB to get the description too, instead of having the data available after SubClassB.run, but at least we're not keeping unneeded data, if it's not requested. We keep it only if it's requested.
class SuperClass:
def __init__(self):
self._id = None # Assigned in run()
self._description = None
#property
def description(self):
raise NotImplementedError
def run(self):
raise NotImplementedError
#property
def id(self):
return self._id
class SubClassA(SuperClass):
def __init__(self):
super().__init__()
self._api_obj = ApiObj1()
#property
def description(self):
if not self._description:
_result = self._api_obj.get_data()
self._description = _result["description"]
return self._description
def run(self, *args):
_result = self._api_obj.trigger_run(foo="foo")
self._id = _result["RunId"]
class SubClassB(SuperClass):
def __init__(self):
super().__init__()
self._api_obj = ApiObj2()
#property
def description(self):
if not self._description:
_result = self._api_obj.get_info()
self._description = _result["description"]
return self._description
def run(self, *args):
_result = self._api_obj.trigger(foo="foo", bar="bar", arg1="arg1", arg2="arg2")
self._id = _result["data"]["id"]

Related

How can I get a class parameter from a function of class via decorator class?

The problem:
I want to get an attribute of class via decorator which is a class but I can not.
The question is how can?
class DecoratorClass:
def __call__(self, fn, *args, **kwargs) -> Callable:
try:
# do something with the TestClass value
return fn
finally:
pass
class TestClass:
def __init__(self):
self.value = 1
#DecoratorClass()
def bar(self):
return 1
How can I reach the the TestClass's value attr via DecoratorClass?
I got the solution :)
class Decoratorclass:
def __call__(self, fn, *args, **kwargs) -> Callable:
def decorated(instance):
try:
# do something with the TestClass value
print(instance.value)
return fn(instance)
finally:
pass
return decorated
class TestClass:
def __init__(self):
self.value = 1
#Decoratorclass()
def bar(self):
return 1

Can I add attributes to class methods in Python?

I have a class like this:
class A:
def __init__(self):
self.size=0
def change_size(self,new):
self.size=new
I want to add an attribute to the change_size method to say what it changes - i.e. so that
A(blah)
blah.change_size.modifies
returns
'size'
is this possible? I have tried:
class A:
def __init__(self):
self.size=0
def change_size(self,new):
self.change_size.modifies = 'size'
self.size=new
nope
class A:
def __init__(self):
self.size=0
self.change_size.modifies = 'size'
def change_size(self,new):
self.size=new
nope
class A:
def __init__(self):
self.size=0
def change_size(self,new,modifies='size'):
self.size=new
none of which seem to work.
That's simple enough. It goes basically the same way you'd add attributes to any other function:
class A:
def __init__(self):
self.size=0
def change_size(self,new):
self.size=new
change_size.modifies = 'size'
print(A.change_size.modifies) # prints size
A more universal solution with a help decorator.
from functools import wraps
def attributes(**attrs):
def decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
for attr_name, attr_value in attrs.items():
setattr(wrapper, attr_name, attr_value)
return wrapper
return decorator
and rewrite you class as
class A:
def __init__(self):
self.size = 0
#attributes(modifies='size')
def change_size(self, new):
self.size = new

Python abstract attribute and inheritance

Say I have the following code:
class Archive(object):
""" Archiv-File wrapper """
READ_MODE = 0
WRITE_MODE = 1
def __init__(self, file_):
self.file_ = file_
self._mode = None
#property
def mode(self):
return self._mode
#mode.setter
def mode(self, value):
self._mode = value
def open(self, mode="r", pwd=None):
raise NotImplemented("Subclasses should implement this method!")
def close(self):
raise NotImplemented("Subclasses should implement this method!")
################################################
class GzipGPGArchive(Archive):
READ_MODE = 'r:gz' # Open for reading with gzip compression.
WRITE_MODE = 'w:gz' # Open for gzip compressed writing.
SUFFIX = "tar.gz.gpg"
def __init__(self, *args, **kwargs):
super(GzipGPGArchive, self).__init__(*args, **kwargs)
#mode.setter # This causes unresolved reference
def mode(self, value):
# do internal changes
self._mode = value
def open(self):
pass
def close(self):
pass
so know what is the best pythonic way to override the setter and getter method of the Abstract class attribute mode.
Overriding #mode.setter in the sub-class GzipGPGArchive causes unresolved reference!
First of all, there is no such thing as abstract attributes in Python. You can achieve abstraction, however, by using abc module. Perhaps it is not really "pythonic", but it works.
This is the minimal example with inheritance and abstraction. Use it as as template:
from abc import ABCMeta, abstractmethod
class Mother(metaclass=ABCMeta):
#abstractmethod
def method_(self):
pass
#property
#abstractmethod
def property_(self):
return -1
#property_.setter
#abstractmethod
def property_(self, value):
pass
class Daughter(Mother):
def __init__(self):
self.value_ = 0
def method_(self):
print(self.value_)
#property
def property_(self):
return = self.value_
#property_.setter
def property_(self, value):
self.value_ = value

How to make this code work in python 2

class Event(metaclass=ABCMeta):
def __init__(self):
self.type = self.get_full_type()
#classmethod
def get_full_type(cls):
return None
def as_dict(self):
return self.__dict__
class BaseEvent(Event, metaclass=ABCMeta):
SUB_TYPE = ''
#classmethod
def get_base_type(cls):
return super().get_full_type()
#classmethod
def get_full_type(cls):
base_type = cls.get_base_type()
if base_type:
return '.'.join([base_type, cls.SUB_TYPE])
else:
return cls.SUB_TYPE
Here you can see my attempt to make class that represents some abstract event. What is crucial here is the ability to distinguish event types. So every event has it's type and it's base type. Full type is base type + subtype.
This gives the ability to define new event type like this
class MockEvent(BaseEvent):
SUB_TYPE = 'mock'
def __init__(self, some_object):
super(self.__class__, self).__init__()
self.some_object = some_object
So the full type is mirroring the class hierarchy ClassA.ClassB.ClassC etc. I think you get the point.
Unfortunately this is not working with python 2
class Event(object):
__metaclass__ = ABCMeta
SUB_TYPE = None
def __init__(self):
self.type = self.get_full_type()
#classmethod
def get_base_type(cls):
return None
#classmethod
def get_full_type(cls):
base_type = cls.get_base_type()
if base_type:
return '.'.join([base_type, cls.SUB_TYPE])
else:
return cls.SUB_TYPE
def as_dict(self):
return self.__dict__
class BaseEvent(Event):
__metaclass__ = ABCMeta
SUB_TYPE = ''
#classmethod
def get_base_type(cls):
return super(cls.__class__, cls).get_full_type()
File "/opt/leos/code/event_service/events/EventBus.py", line 38, in
get_base_type
return super(cls.class, cls).get_full_type()
AttributeError: 'super' object has no attribute 'get_full_type'
How can I make this work?
class Event(object):
__metaclass__ = ABCMeta
def __init__(self):
self.type = self.get_full_type()
#classmethod
def get_full_type(cls):
return None
def as_dict(self):
return self.__dict__
class BaseEvent(Event):
__metaclass__ = ABCMeta
SUB_TYPE = None
#classmethod
def get_full_type(cls):
super_type = cls.get_super_type()
base_type = super_type.get_full_type()
if base_type:
return '.'.join([base_type, cls.SUB_TYPE])
else:
return cls.SUB_TYPE
#classmethod
def get_super_type(cls):
return cls.__base__
I needed to get the base type automatically. Without mentioning current class in super(currectClass, self) So I used cls.base and it's working ok.

Notify parent instance about property change

I have these two classes:
class Status(object):
def __init__(self):
self._message = ''
#property
def message(self):
return self._message
#message.setter
def message(self, value):
self._message = value
class Buddy(object):
def __init__(self, name):
self.name = name
self.status = Status()
def status_updated(self):
# this should be called when self.status.message is changed
and I use them like this:
buddy = Buddy('John')
buddy.status.message = 'Hello world!' # this should call Buddy.status_updated
I want Buddy.status_updated to be called when I modify the message property of Status. How to achieve this?
You'll have to store a reference back to the parent; python values do not track where they are stored (there can be multiple places that refer to your Status() instances):
class Status(object):
def __init__(self, parent=None):
self._message = ''
self._parent = parent
#property
def message(self):
return self._message
#message.setter
def message(self, value):
self._message = value
if self._parent is not None:
self._parent.status_updated()
class Buddy(object):
def __init__(self, name):
self.name = name
self.status = Status(self)
def status_updated(self):
# this should be called when self.status.message is changed
In each case you need somehow to register you want to "listen" to other's object property changes. I would suggest simple solution like this:
class Status(object):
def __init__(self, on_message_change=None):
self._message, self._on_message_change = '', on_message_change
#property
def message(self):
return self._message
#message.setter
def message(self, value):
if self._on_message_change:
self._on_message_change(self._message, value)
self._message = value
class Buddy(object):
def __init__(self, name):
self.name = name
self.status = Status(self.status_updated)
def status_updated(self, old_value, new_value):
print("status changed '%s' -> '%s'" % (old_value, new_value))
b = Buddy("someone")
b.status.message = "init"
b.status.message = "new"
output is:
status changed '' -> 'init'
status changed 'init' -> 'new'

Categories