Since you probably don't really want to modify the attributes of your object for a poorly defined interval, you need to return or otherwise create a different object.
The simplest case would be one in which you had two separate objects, and no __call__ method at all:
d1_obj = DateCalc()
d2_obj = DateCalc('2/2/2002')
print(d1_obj.getday()) # 1/1/2001
print(d2_obj.getday()) # 2/2/2002
If you know where you want to use d_obj vs d_obj() in the original case, you clearly know where to use d1_obj vs d2_obj in this version as well.
This may not be adequate for cases where DateCalc actually represents a very complex object that has many attributes that you do not want to change. In that case, you can have the __call__ method return a separate object that intelligently copies the portions of the original that you want.
For a simple case, this could be just
def __call__(self, day=DEFAULT):
return type(self)(day)
If the object becomes complex enough, you will want to create a proxy. A proxy is an object that forwards most of the implementation details to another object. super() is an example of a proxy that has a very highly customized __getattribute__ implementation, among other things.
In your particular case, you have a couple of requirements:
- The proxy must store all overriden attributes.
- The proxy must get all non-overriden attributes from the original objects.
- The proxy must pass itself as the
self parameter to any (at least non-special) methods that are invoked.
You can get as complicated with this as you want (in which case look up how to properly implement proxy objects like here). Here is a fairly simple example:
# Assume that there are many fields like `day` that you want to modify
class DateCalc:
DEFAULT= "1/1/2001"
def __init__(self, day=DEFAULT):
self.day= day
def getday(self):
return self.day
def __call__(self, **kwargs):
class Proxy:
def __init__(self, original, **kwargs):
self._self_ = original
self.__dict__.update(kwargs)
def __getattribute__(self, name):
# Don't forward any overriden, dunder or quasi-private attributes
if name.startswith('_') or name in self.__dict__:
return object.__getattribute__(self, name)
# This part is simplified:
# it does not take into account __slots__
# or attributes shadowing methods
t = type(self._self_)
if name in t.__dict__:
try:
return t.__dict__[name].__get__(self, t)
except AttributeError:
pass
return getattr(self._self_, name)
return Proxy(self, **kwargs)
The proxy would work exactly as you would want: it forwards any values that you did not override in __call__ from the original object. The interesting thing is that it binds instance methods to the proxy object instead of the original, so that getday gets called with a self that has the overridden value in it:
d_obj = DateCalc()
print(type(d_obj)) # __main__.DateCalc
print(d_obj.getday()) # 1/1/2001
d2_obj = d_obj(day='2/2/2002')
print(type(d2_obj)) # __main__.DateCalc.__call__.<locals>.Proxy
print(d2_obj.getday()) # 2/2/2002
Keep in mind that the proxy object shown here has very limited functionality implemented, and will not work properly in many situations. That being said, it likely covers many of the use cases that you will have out of the box. A good example is if you chose to make day a property instead of having a getter (it is the more Pythonic approach):
class DateCalc:
DEFAULT= "1/1/2001"
def __init__(self, day=DEFAULT):
self.__dict__['day'] = day
@property
def day(self):
return self.__dict__['day']
# __call__ same as above
...
d_obj = DateCalc()
print(d_obj(day='2/2/2002').day) # 2/2/2002
The catch here is that the proxy's version of day is just a regular writable attribute instead of a read-only property. If this is a problem for you, implementing __setattr__ appropriately on the proxy will be left as an exercise for the reader.