6

我试图将一些函数屏蔽为属性(通过这里不重要的包装器)并将它们动态添加到对象中,但是,我需要代码完成和 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"
4

3 回答 3

1

修复你的IDE?:-)。在 Python 中,总会出现静态分析无法进行的极端情况。在这种情况下,您获得了本应帮助您解决问题的工具。

如果不运行代码,IDE 或 Mypy 都无法找到这些动态属性。我知道有一些 IDE,至少在过去,它们实际上是通过导入一个模块来实现自动完成——但这也可能引发一系列附带影响。

我会说你必须在没有这些工具的情况下才能拥有你的动态代码——到添加带有“不检查这个”标记样式的注释的地步。根本不可能自动完成。

于 2017-03-21T13:36:25.873 回答
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 对象中,如果原始数据为 none,则 getter 返回默认值,如果有,则仅当 getter args 没有' t 改变行为以忽略默认值(第二个参数)... getter 的第一个参数是覆盖默认值...

所以 self.GetP( ) where self._p == None and self.GetPDefaultValue( ) is 12.34 将返回 12.34 和 self.GetP( 24.56 ) 将返回 24.56 和 self.GetP( '没关系', True ) 将返回 None因为ignore_defaults值(即如果设置了一个值,那么它将返回第二个参数设置为True的值......但由于没有设置任何值,所以返回None)......

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

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

如果您查看我的回复等...。您会看到更大的功能列表,以及更多...例如:自动设置初始化函数,设置分组系统,所以如果我创建一个组( All ) 按 p, y, r 的顺序...然后执行 this.SetAll( p, y, r ); 然后全部分配。我需要验证没有命名冲突,但目标是减少需要多少代码。

于 2018-10-24T13:05:23.127 回答
0

你可以尝试类似的东西

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

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

于 2020-01-17T14:33:03.660 回答