例如,
# 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
最后,问题的关键是:
# 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
并且在各种各样的下游文件中:
from deep.internal.interface import upgraded_foo
upgraded_foo.do_fabble(...)
以及各种各样的遗留文件:
from deep.internal.TheFoos import TheFoos
TheFoos.do_something_configured_just_once_in_new_code()
因此回到僵局:
upgraded_foo = UpgradedFoo(foo) # the c++-ish way
# but what is the python way?
但在python中,这是怎么实现的呢?
我尝试过:from typing import cast; cast(UpgradedFoo, foo)
,但没有成功。我也尝试过研究这个问题,但这似乎是专家和专家之间的症结所在,而且没有真正好的答案可以直接面对和解决这个问题。
我们的想法是让这个功能发挥作用,这样就没有循环导入,代码参数都基于单个配置,而不会在代码中引发巨大的变化传播。
请注意,虽然以上不是某种理想的架构,但它正沿着将许多作者的旧作品与新作品相结合的理想架构的道路前进。
抛开动机不谈,这里需要将基类实例上转换为继承的类实例。
关于";C++方式";(所有细节(:
假设存在基类和派生类;数据";基类所持有的与派生类相同——;额外的";派生类中相对于基类的数据的排序。
然后可以生成一个指针,并将其强制转换为派生类的派生类功能。仅指针";关心";关于实例数据以及二进制函数在哪里。编译器与该数据相关联的实际函数束可以通过强制转换操作升级到派生类上下文。
#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
所以我认为这是python
中的一个哲学选择,但它看起来像是部署了一个改变传播的推送模型,没有出路。
如果有一个基类,并且子类将该基类内容拉入其中:
class Foo:
...
class DeepFoo(Foo):
...
然后,如果在某些上下文中DeepFoo
要封装在初始化的Foo
周围,则DeepFoo
必须知道如何通过将Foo
的数据字典反转为Foo
的输入约定来从Foo
初始化自己,否则将丢失根类构造函数:
class DeepFoo(Foo):
def __init__(self, foo: Foo): # overriding the root constructor
self.__dict__ = deepcopy(foo.__dict__)
此外,如果DeepFoo.from_foo(...)
是可能的并且被定义,则Foo
不能向DeepFoo
通知其自身的变化——DeepFoo
负责发现并跟上Foo
,以便在DeepFoo
被封装在预先存在的Foo
周围时,具有所有继承的Foo
功能正常工作所需的数据。
尽管python
是一种动态语言,但动态铸造是不可用的,并且不能简单地"铸造";拥有";另一个类的数据内容,而实际上没有经过构建过程。
这意味着,当代码达到这一点时,无论出于何种原因(例如困难的反向工程位置(,都需要将一个类拆分为基类和子类,可能有必要转移到嵌套的休眠包装器模型,该模型虽然类似于第一次复制方法,但至少是它所声称的:
class Wrapper:
def __init__(self, item: Any):
self.__item = item
def __getattr__(self, item: str) -> None:
return eval(f"self.item.{item}")
class DeepFoo(Wrapper):
def __init__(self, foo: Foo):
self.super().__init__(foo)
def do_fabble(self, args):
# use self.item here!
而且,在这种情况下,IDE intellisense现在断开了连接,因此DeepFoo
可以访问的Foo
方法是private
(或休眠(,但它们在动态评估期间存在。然而,这里的包装器可以充当占位符,这不会产生误导,因为函数用于替换下游代码中的DeepFoo.do_fabble
。
我想到的第三种替代方案;"黑客术";击键是为了保留DeepFoo
名称,但使其仅包含@staticmethods
# So
class DeepFoo(Foo):
def do_fabble(self, ...):
pass
# becomes
class DeepFoo:
@staticmethod
def do_fabble(self: Foo, ...):
...
对DeepFoo
依赖关系的所有下游更改仍在传播,但相对来说是偶然的,并且只传播一次。
最后一种选择让我克服了所有导入问题,将我的遗留代码绑定到我的新配置文件中,并在不生成大量难以理解的人工或代码的情况下阻止了大型重构。