面向对象设计
我有一个包含许多不同条目的数据库存储,但每个条目可能有一个不同的" object_type" (其中不同的属性存储在数据库中)。对于每个" object_type"我有一个自己的python类 - 请参阅下面的简化代码示例。通常我需要在每个请求中实例化数千个子类对象,因此DataObject.get_instances()
允许我立即执行此操作,因为它最大限度地减少了数据库访问时间,但这会导致我的OOP设计出现一些问题,所以我有两个问题/问题:
1。如何让超类知道它的子类作为硬编码似乎不是很优雅,通常超类也不应该知道它的子类?
2. 如何在不创建循环导入语句的情况下将子类外包到不同的文件中(因为每个子类需要导入DataObject
并且DataObject
需要导入每个子类以某种方式)?
data_object.py
import pandas as pd
# mimics Database module behaviour ==========================================
df_common = pd.DataFrame({'object_id': [1, 3, 2, 4, 5],
'object_type': ['Apple', 'Apple', 'Banana',
'Banana', 'LittleBanana']})
db_details = {1: {'object_type': 'Apple', 'sort': 'Jazz', 'object_id': 1},
2: {'object_type': 'Banana', 'country': 'Brazil',
'object_id': 2},
3: {'object_type': 'Apple', 'sort': 'Gala', 'object_id': 3},
4: {'object_type': 'Banana', 'country': 'Vietnam',
'object_id': 4},
5: {'object_type': 'LittleBanana', 'country': 'Vietnam',
'object_id': 4}}
def db_get_details(id_list, attributes, cls_name):
# pseudo db query function
print('Getting attributes {} (class: {}) '.format(attributes, cls_name))
entries = list()
for id_ in id_list:
entries.append(db_details[id_])
return entries
def db_get_base_data(id_list):
# pseudo db query function
id_data = pd.DataFrame({'object_id': id_list})
return id_data.merge(df_common, on='object_id', how='left')
# Classes =========================================
class MetaDataObject(type):
sub_classes = {}
def __init__(cls, name, bases, d):
type.__init__(cls, name, bases, d)
MetaDataObject.sub_classes[cls.__name__] = cls
class DataObject(object, metaclass=MetaDataObject):
attributes = ['object_type']
def __init__(self, object_id, object_type):
self.object_id = object_id
self.object_type = object_type
@classmethod
def get_instances(cls,id_list=None):
# returns a list of sub-class instances
# list storage of object instances
res = list()
# get detailed data from database
db_common = db_get_base_data(id_list)
# apply group_by (via data_type)
for object_type, tmp_df in db_common.groupby(by='object_type'):
# get the specific object_type class
sub_class = DataObject.sub_classes[object_type]
# check if objects come from desired classes
if issubclass(sub_class, cls) is False:
raise TypeError(
'ID of class {}, cannot be called from {}'.format(
sub_class.__name__, cls.__name__))
# get class specific data
sub_class_data = sub_class.get_attributes(
tmp_df['object_id'].tolist())
# instantiate
for entry_dict in sub_class_data:
res.append(sub_class(**entry_dict))
return res
@classmethod
def get_attributes(cls, id_list):
# query db with cls specific attributes
return db_get_details(id_list, cls.attributes, cls.__name__)
class Apple(DataObject):
attributes = ['sort']
def __init__(self, object_id, object_type, sort=None):
super().__init__(object_id, object_type)
self.sort = sort
class Banana(DataObject):
attributes = ['country']
def __init__(self, object_id, object_type, country=None):
super().__init__(object_id, object_type)
self.country = country
class LittleBanana(Banana):
pass
if __name__ == '__main__':
ids = [1, 2, 3, 1, 2, 1, 3, 4, 2, 1, 2, 3, 4, 5]
DataObject.get_instances(ids)
Apple.get_instances(ids)
sub_object.py
import data_object as do
class SpecialApple(do.Apple):
pass
编辑I:我怀疑我需要在另一个独立的对象中拆分get_instances,其中数据库访问是优化的,并且实际的实例化是在瓶颈是数据库访问时发生的。我希望把它解决成一个类的树,但我想这几乎不可能。
编辑II:我已经通过添加更多细节来更新示例(回答问题1 的隐含性),以说明为什么我使用这种设计方法尝试解决它的问题。
编辑III:如果子类在另一个文件中,则添加一个明确的示例。由于meta_class方法无法解决问题,因此SpecialApple
不是在data_object.py中创建的meta_class dict的一部分。导入sub_object
也无法解决问题。是否会在元类中注册SpecialApple
的任何特殊导入?
答案 0 :(得分:2)
我可能误会了,但我认为如果你的超级班级确实需要知道你的设计,那么你的设计就会被彻底打破。哪些类是作为子类创建的 - 这不是继承的工作方式。
为什么您认为您的DataObject需要导入有关子类的任何内容?
在超类中,当您使用select dnumber as department_number,
count(case de.relationship when 'SON' then 1 end) as sons,
count(case de.relationship when 'DAUGHTER' then 1 end) as daughters,
count(case de.relationship when 'SPOUSE' then 1 end) as spouses
from [... the rest of your query here]
作为对实例的引用时,可以选择超类或子类,具体取决于您实际创建的那个。
一个简单的例子就足够了:
self
正如您所看到的 - SuperClass调用class Superclass(object):
def process_step1(self):
print "step 1 process1"
return self.process_step2()
def process_step2(self):
print 'You shouldn't call me'
return 0
class MyProcess(SuperClass):
def start(self):
self.process_step1()
def process_step2(self):
print "My Process - Step 2"
return 1
>>> proc = MyProcess()
>>> proc.start()
step 1 process1
My Process - Step 2
1
,但在示例中,self是self.process_step2()
的实例 - 因此它将调用MyProcess
- 这就是继承的意思工作
如果你真的需要你的超类了解所有的超类,有三种pythonic方法可以做到:
MyProcess.process_step2
或__new__
调用。另外 - 除非模块实际上需要在代码中按名称引用类,否则您的模块不需要导入任何内容 - 如果您有对实例的引用 - 您的代码可以访问该实例的属性和方法而无需导入任何东西。
最后(一般来说)双重进口通常不是'通常'一个问题 - 蟒蛇是聪明的'足以识别模块何时已导入 - 而不是再次执行。如果您使用不同的路径导入相同的模块,这有时会中断,但通常情况下这不是问题,除非您使用像Django这样的框架,它在创建模型时会做出聪明的事情。
答案 1 :(得分:0)
我肯定会有另一个对象,例如DataObjectStore
来处理抽象数据库的责任,而DataObjectStore
可以使用DataObjectFactory
从数据库结果集重新构建对象或任何其他代表。
工厂可以使用反射根据类型字符串动态地实例化正确的类并设置所有属性。如果属性映射过程很简单,那么可以很好地工作。
对于复杂的数据结构,我会研究Python的现有ORM,或者我甚至考虑存储序列化数据。根据您的查询需求,以JSON或其他数据交换格式序列化数据也可以很好地工作。
最后,如果您不仅限于关系数据库,那么您可以在此处查看NoSQL DB可能会有所帮助。
答案 2 :(得分:0)
如何让超类了解其子类
在一般情况下,不要。让第三部分用
处理SuperClass.__subclasses__()
如何在不创建圆圈导入的情况下将子类外包到不同的文件中
application.py
from super_class import SuperClass
from sub_class_1 import SubClass1
from sub_class_2 import SubClass2
sub_objects = []
for SubClass in SuperClass.__subclasses():
sub_objects.append(SubClass())
super_class.py
SuperClass():
pass
sub_class_1.py
from super_class import SuperClass
SubClass1(SuperClass):
pass