Python动态属性和mypy

时间:2017-03-20 12:00:34

标签: python metaclass mypy

我试图将一些函数作为属性进行掩码(通过这里不重要的包装器)并将它们动态添加到对象中,但是,我需要代码完成和mypy才能工作。

我想出了如何动态添加属性(通过元类或只是在构造函数中),但我遇到的问题是mypy没有拿起它(IDE​​也没有)。

一种解决方法是定义具有相同名称/类型的属性,但我真的不喜欢这种方法(代码太多,静态属性集,重复)。

有更好的方法吗?

class Meta(type):
    def __new__(cls, clsname, bases, dct):

        def prop(self) -> int:
            return 1

        inst = super(Meta, cls).__new__(cls, clsname, bases, dct)
        inst.dynprop=property(prop)
        return inst

class Foo(metaclass=Meta):
    dynprop=int #this works, but I don't want it

class Bar(metaclass=Meta):pass

def somefunc(s:str):
    print(s)

foo=Foo()
bar=Bar()
somefunc(foo.dynprop)   #this is ok
somefunc(bar.dynprop)   #meta.py:24: error: "Bar" has no attribute "dynprop"

3 个答案:

答案 0 :(得分:1)

修复你的IDE? :-)。在Python中,总会出现静态分析无法进行的极端情况。在这种情况下,你得到的工具应该可以帮助你阻止你。

如果不运行代码,IDE或Mypy无法找到这些动态属性。我知道有IDE,至少在过去,使用实际导入模块进行自动完成 - 但这也可以触发大量的附带效果。

我会说你必须没有这些工具才能拥有动态代码 - 用“不检查这个”标记样式添加注释。自动完成功能根本不可能。

答案 1 :(得分:0)

这是我的回复:https://www.dropbox.com/s/skj81l6upddrqpy/dynamic_properties_information.txt?dl=0

这是我在Python中为动态AccessorFuncs / Properties实现的新版本的旧版本:https://www.dropbox.com/s/6gzi44i7dh58v61/dynamic_properties_accessorfuncs_and_more.py?dl=0

最新消息在我的库中,链接在此文本文件的最上方...

基本上,您可以这样做:

##
## Angle Base Class - Note: I could parent the same way Vector does and change the getattr magic function to alter x / y / z to p / y / r for pitch, yaw and roll without re-declaring anything...
##
class AngleBase( Pos ):
    pass;
class Angle( AngleBase ):
    ##
    __name__                    = 'Angle';

    ## Note, I'm using self.p / y / r for to string data instead of functions because if I rename the property from x, y, z dynamically without re-declaring then it means I'd either need to rename all of the functions too, or just re-declare, or simply use self.p / y / r instead, everywhere...
    ## Task: Add system to rename functions in this regard to allow prevention of adding so much duplicate code...
    __serialize                 = AccessorFuncBase( parent = AngleBase, key = 'serialize',                                  name = 'Serialize',                                         default = 'Angle( 0.0, 0.0, 0.0 );',        getter_prefix = '',             documentation = 'Serialize Data',           allowed_types = ( TYPE_STRING ),                    allowed_values = ( VALUE_ANY ),                 setup = { 'get': ( lambda this: 'Angle( ' + str( this.p ) + ', ' + str( this.y ) + ', ' + str( this.r ) + ' );' ) }             );

    ## Note: I could set up pitch, yaw, roll with Get / Set redirecting to p / y / r.... This would make __pitch, __yaw, and __roll available... But I don't want to override pitch / yaw / roll / _pitch / _yaw / _roll created by these 3 aliases... So I'll likely just need to add the alias system for names too.. Honestly, I should change the defaults to Pitch / Yaw / Roll and add P / Y / R as the aliases..
    __p                         = AccessorFuncBase( parent = AngleBase, key = 'p',              keys = [ 'pitch' ],         name = 'Pitch',             names = [ 'P' ],                default = 0.0,                              getter_prefix = 'Get',          documentation = 'Pitch',                    allowed_types = ( TYPE_INTEGER, TYPE_FLOAT ),       allowed_values = ( VALUE_ANY )      );
    __y                         = AccessorFuncBase( parent = AngleBase, key = 'y',              keys = [ 'yaw' ],           name = 'Yaw',               names = [ 'Y' ],                default = 0.0,                              getter_prefix = 'Get',          documentation = 'Yaw',                      allowed_types = ( TYPE_INTEGER, TYPE_FLOAT ),       allowed_values = ( VALUE_ANY )      );
    __r                         = AccessorFuncBase( parent = AngleBase, key = 'r',              keys = [ 'roll' ],          name = 'Roll',              names = [ 'R' ],                default = 0.0,                              getter_prefix = 'Get',          documentation = 'Roll',                     allowed_types = ( TYPE_INTEGER, TYPE_FLOAT ),       allowed_values = ( VALUE_ANY )      );


    ##
    ## This isn't necessary... As the defaults are already 0.0, using the getter or the property will return that value... This is a convenience function to allow assigning all values at once...
    ##
    def __init__( self, _pitch = 0.0, _yaw = 0.0, _roll = 0.0 ):
        ## Update all of the properties - Note: I could use self.SetPitch( _pitch ), self._p = _pitch, and a few other options. I'm using short-hand here for sake of efficiency... But self.SetPitch( ... ) is actually called when self.p / self.pitch is reassigned with _pitch...
        self.p = _pitch;
        self.y = _yaw;
        self.r = _roll;

首先需要让类存在的要求是meh-我正在寻找一种替代方法,但是即使我在 init 期间创建访问器,也已经遇到了问题,即使对这些函数的调用总是在 init 之后。...因此,通过这种方式,我保留了带有其他选项的完整动态功能,并且可以使用函数形式或属性形式。

即:self.p = 1234.03与self.SetP(1234.03)和print(str(self.p))相同;与print(str(self.GetP()))或print(self.GetPToString())等相同...

self.key ==属性 self._key ==存储的原始数据,默认为None-默认值存储在AccessorFunc对象中,如果raw为none,则getter返回默认值,如果有,则仅当getter args不改变要忽略的行为时默认值(第二个arg)...吸气剂的第一个参数是覆盖默认值...

因此self.GetP(),其中self._p ==无,self.GetPDefaultValue()为12.34将返回12.34,self.GetP(24.56)将返回24.56,self.GetP('无所谓',True)会由于ignore_defaults值而返回None(即,如果设置了一个值,则它将返回第二个arg设置为True的值...但是由于未设置任何值,因此返回None)...

还有数据类型和值保护,因此您可以确保分配给值的是iff(仅当且仅当)数据类型和/或值才被授权...如果不是,则为忽略了。

此系统增加了很多自由。我将很快添加更多功能。

如果您查看我的回复等。。您会看到更大的功能列表,还有更多...例如:自动设置 init 功能,设置设置分组系统,以便如果我按该顺序创建一个具有p,y,r的组(全部)...则执行此操作。SetAll(p,y,r);那么我将需要验证所有命名冲突,但目的是减少所需的代码量。

答案 2 :(得分:0)

您可以尝试类似的

T = TypeVar('T', bound=Meta)
bar: T = Bar()
somefunc(bar.dynprop)

这不会检查(或抱怨)您的动态属性,但至少它 可以知道非动态继承的属性。