For example,
# low level file Foo.py
class Foo:
def __init__(...):
# a class that is difficult to construct, as it is connected to configuration
# files and schemas
# it is also useful to import and use early prior to the interpretation of other
# classes since it is a configured entity
# resource file resource.py
cfg = load_config('config.json')
foo = Foo.from_config(cfg)
# enum file TheFoos.py
from resource import foo
class TheFoos:
A = 'a'
B = 'b'
_ctx = foo.ctx
# descendant class with useful methods in UpgradedFoo.py
from Foo import Foo
class UpgradedFoo(Foo):
def do_fabble(self, ...):
pass
Finally, the crux of the question:
# interface for end user interface.py
from resources import foo
from UpgradedFoo import UpgradedFoo
upgraded_foo = UpgradedFoo(foo) # the pythonic pseudo code of the C++ way
And in a large variety of downstream files:
from deep.internal.interface import upgraded_foo
upgraded_foo.do_fabble(...)
And a large variety of legacy files:
from deep.internal.TheFoos import TheFoos
TheFoos.do_something_configured_just_once_in_new_code()
So winding back around to the impasse:
upgraded_foo = UpgradedFoo(foo) # the c++-ish way
# but what is the python way?
But what is the way this is done in python?
I have tried: from typing import cast; cast(UpgradedFoo, foo)
, but that did not work. I have also tried researching the question, but this seems to be a sticking point between the experts and the noobs, and there is no real good answer that actually confronts and addresses the issue directly.
The idea is to get this functioning so there are no circular imports and code parameters are all based on a single configuration without inducing tremendous change propagation in the code.
Please note that while the above is not some sort of ideal architecture, it is along the path to an ideal architecture having integrated old work from many authors with new work.
Motivation aside, the need here is to upcast the base class instance to the inherited class instance.
Note on the "C++ way" (all details):
Suppose a base class and a derived class exist, and that the "data" held by the base class is identical to the derived class -- there is no "extra" sort of data in the derived class relative to the base class.
Then a pointer can be generated and cast to the derived class for derived class functionality. The pointer only "cares" about the instance data and where the binary functions are. The actual bundle of functions associated by the compiler with that data can be upgraded to derived class context w/ the cast operation.
#include<iostream>
class A {
public:
int foo;
A(int x): foo(x){};
};
class B : public A {
public: int bar() {return this-> foo * 2;};
};
int main() {
A a = A(1);
A* astar = &a;
B* bstar = (B*) &a;
B b = *bstar;
// symbolic cast
B b2 = *(B*)&a;
std::cout << astar << std::endl;
std::cout << bstar << std::endl;
std::cout << bstar->bar() << std::endl;
std::cout << b.bar() << std::endl;
std::cout << b2.bar() << std::endl;
}
// output:
0x#####
0x#####
2
2
2