Python 动态属性和mypy

Python dynamic properties and mypy

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

我想出了如何动态添加 属性(通过元类或简单地在构造函数中),但我遇到的问题是 mypy 没有选择它([=21 也没有=]).

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

有没有更好的方法?

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"

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

没有 运行 代码,IDE 或 Mypy 都无法找到这些动态属性。我知道有 IDEs,至少在过去,采用实际导入模块来自动完成 - 但这也会引发许多附带影响。

我想说您将不得不在没有这些工具的情况下生活,以便拥有您的动态代码 - 甚至可以使用 "don't check this" 标记样式添加注释。根本无法自动完成。

这是我的回复: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

最新的在我的图书馆里,link在最上面的这个文本文件里...

基本上,它可以让你这样做:

##
## 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;

需要 class 首先存在的要求是 meh - 我正在寻找替代方案,但如果我在 init[=38] 期间创建访问器,我已经遇到了问题=] 即使对这些函数的调用总是在 init...之后或 属性 表格..

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

self.key == 属性 self._key == 存储的原始数据,默认为 None - 默认值存储在 AccessorFunc 对象中,如果原始数据为 [=,则 getter return 为默认值63=],如果只有 getter args 不改变忽略默认值的行为(第二个 arg)... getter 的第一个 arg 是覆盖默认值...

所以 self.GetP( ) 其中 self._p == None 和 self.GetPDefaultValue( ) 是 12.34 将 return 12.34 和 self.GetP ( 24.56 ) 会 return 24.56 而 self.GetP( 'does not matter', True ) 会 return None 因为 ignore_defaults 值(即如果一个值被设置然后它将 return 第二个 arg 设置为 True 的值...但是由于没有设置值,None 是 returned )...

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

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

如果您看一下我的回复等...您会看到更多即将推出的功能,以及更多...例如:自动设置 init 函数,设置分组系统,所以如果我按顺序用 p、y、r 创建一个组(全部)...然后执行 this.SetAll( p, y, r );那么所有的都将被分配。我需要验证没有命名冲突,但目标是减少需要多少代码。

你可以试试

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

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