python的元类实用程序
Py-Meta-Utils的Python项目详细描述
py元实用程序
有用链接
作为库的元选项工厂模式,以及相关的元类实用程序
好的,但是元选项工厂模式是什么?也许最简单的解释方法是从一个例子开始。假设您希望您的最终用户能够有选择地从您正在编写的库中记录类的操作:
classEndUserClass(YourLoggableService):classMeta:debug:bool=Trueverbosity:int=2log_destination:str='/tmp/end-user-class.log'
第一步是定义自定义的MetaOption子类:
- 实现所绝对需要的是构造函数及其
name
参数。也就是说,为了显式起见,建议还指定default
和inherit
参数。 check_value
方法是可选的,但对于确保用户不会给您垃圾很有用。get_value
方法有一个默认实现,通常不需要重写,除非默认值是可变的或具有高级逻辑。- 还有一个
contribute_to_class
方法,我们稍后将讨论。
importosimportsys# first we have to import what we need from py_meta_utilsfrompy_meta_utilsimport(McsArgs,MetaOption,MetaOptionsFactory,process_factory_meta_options,_missing)# then we have to declare the meta options the meta options factory should supportclassDebugMetaOption(MetaOption):def__init__(self):super().__init__(name='debug',default=False,inherit=True)defcheck_value(self,value,mcs_args:McsArgs):ifnotisinstance(value,bool):raiseTypeError(f'The {self.name} Meta option must be a bool')classVerbosityMetaOption(MetaOption):def__init__(self):super().__init__(name='verbosity',default=1,inherit=True)defcheck_value(self,value,mcs_args:McsArgs):ifvaluenotin{1,2,3}:raiseValueError(f'The {self.name} Meta option must either 1, 2, or 3')classLogDestinationMetaOption(MetaOption):def__init__(self):super().__init__(name='log_destination',default=_missing,inherit=True)# this pattern is useful if you need a mutable default value like [] or {}defget_value(self,Meta,base_classes_meta,mcs_args:McsArgs):value=super().get_value(Meta,base_classes_meta,mcs_args)returnvalueifvalue!=_missingelse'stdout'defcheck_value(self,value,mcs_args:McsArgs):ifvaluein{'stdout','stderr'}:returntry:valid_dir=os.path.exists(os.path.dirname(value))exceptException:valid_dir=Falseifnotvalid_dir:raiseValueError(f'The {self.name} Meta option must be one of `stdout`, ''`stderr`, or a valid filepath')
下一步是对MetaOptionsFactory进行子类划分,并指定所需的MetaOption子类:
classLoggingMetaOptionsFactory(MetaOptionsFactory):_options=[DebugMetaOption,VerbosityMetaOption,LogDestinationMetaOption,]
然后您需要一个元类来实际应用工厂选项:
classLoggingMetaclass(type):def__new__(mcs,name,bases,clsdict):mcs_args=McsArgs(mcs,name,bases,clsdict)factory_cls=mcs_args.getattr('_meta_options_factory_class',LoggingMetaOptionsFactory)options_factory=factory_cls()options_factory._contribute_to_class(mcs_args)# the above three lines can be replaced by:# process_factory_meta_options(mcs_args, LoggingMetaOptionsFactory)returnsuper().__new__(*mcs_args)
最后,使用刚刚定义的元类创建公共类:
classYourLoggableService(metaclass=LoggingMetaclass):defdo_important_stuff(self):ifself.Meta.verbosity<3:self._log('doing important stuff')else:self._log('doing really detailed important stuff like so')def_log(self,msg):ifnotself.Meta.debug:returnifself.Meta.log_destination=='stdout':print(msg)elifself.Meta.log_destination=='stderr':sys.stderr.write(msg)sys.stderr.flush()else:withopen(self.Meta.log_destination,'a')asf:f.write(msg)
选项工厂自动将Meta
属性添加到正在构造的类(在本例中为YourLoggableService
)。(在这种情况下,Meta
属性将填充由工厂指定的MetaOption子类提供的默认值。)如果正在构造的类具有部分Meta
类,则将缺少的元选项添加到该类中。(*)
(*)实际上就是这样,出于所有实际目的,可能是您应该如何考虑它,但从技术上讲,正在构造的类的Meta
属性实际上被指定的MetaOptionsFactory子类的填充实例替换。
我们没有涉及的一件事是MetaOption.contribute_to_class
。这是一个可选的回调挂钩,它允许MetaOption
子类为正在构造的类贡献一些东西。它很可能向类添加/从类中删除属性,或者可能用decorator或其他东西包装某些方法。
在包含的源代码中可以找到一个很好的简单示例AbstractMetaOption:
ABSTRACT_ATTR='__abstract__'classAbstractMetaOption(MetaOption):def__init__(self):super().__init__(name='abstract',default=False,inherit=False)defget_value(self,Meta,base_classes_meta,mcs_args:McsArgs):# class attributes take precedence over the class Meta's valueifmcs_args.clsdict.get(ABSTRACT_ATTR,False)isTrue:returnTruereturnsuper().get_value(Meta,base_classes_meta,mcs_args)isTruedefcontribute_to_class(self,mcs_args:McsArgs,value):ifvalueisTrue:mcs_args.clsdict[ABSTRACT_ATTR]=Trueelse:mcs_args.clsdict[ABSTRACT_ATTR]=False
许多库使用__abstract__
类属性来确定构造中的类是否应该被视为具体的,但它们不理解类Meta
选项。因此,我们实现了MetaOption.contribute_to_class
,将__abstract__
类属性设置为与此类库向后兼容的适当值。
包括元类实用程序
单重态
Singleton是一个包含的元类,它使任何使用它的类都成为一个单独的类:
frompy_meta_utilsimportSingletonclassYourSingleton(metaclass=Singleton):passinstance=YourSingleton()assertinstance==YourSingleton()
使用Singleton的类可以是子类,但是,必须通知子类的基类:
frompy_meta_utilsimportSingletonclassBaseSingleton(metaclass=Singleton):passclassExtended(BaseSingleton):passBaseSingleton.set_singleton_class(Extended)base_instance=BaseSingleton()extended_instance=Extended()assertbase_instance==extended_instance==BaseSingleton()==Extended()
深部皮肤
deep_getattr(clsdict,bases,'attr_name',[default])
deep_getattr
的作用与构造类对象上的getattr
一样,只是它在类前构造类字典和基类上操作。换句话说,我们首先在类字典中查找属性,然后搜索所有基类(按方法解析顺序),如果在类字典或基类中找不到属性,则返回默认值(如果未给定属性,则返回AttributeError
)。
optionalmaclass和optionalclass
try:fromoptional_dependencyimportSomeClassexceptImportError:frompy_meta_utilsimportOptionalClassasSomeClassclassOptional(SomeClass):pass
许可证
麻省理工学院