Python dynamic properties and mypy

自闭症网瘾萝莉.ら 提交于 2021-02-08 15:17:05

问题


I'm trying to mask some functions as properties (through a wrapper which is not important here) and add them to the object dynamically, however, I need code completion and mypy to work.

I figured out how to add a property dynamically (either through a metaclass or simply in constructor), but the problem I have is mypy doesn't pick it up (and neither does the IDE).

One workaround is to define an attribute with the same name/type, but I really don't like this approach (too much code, static set of attributes, repetition).

Is there a better way?

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"

回答1:


Fix your IDE? :-). In Python there will always be corner cases where static analysis can't go . In this case you got the tools that ere supposed to be helping you getting in your way.

There is no way for either the IDE or Mypy to find about these dynamic attributes without running the code. I know there are IDEs, that at least in the past, resorted to actually importing a module to have auto-complete - but that also can trigger a host of collateral effects.

I'd say you will have to live without these tools in order to have your dynamic code - to the point of adding comments with the "don't check this" markup style. Autocomplete can't be possible at all.




回答2:


Here is my response: https://www.dropbox.com/s/skj81l6upddrqpy/dynamic_properties_information.txt?dl=0

This is the old version of the new version of my implementation for dynamic AccessorFuncs / Properties in Python: https://www.dropbox.com/s/6gzi44i7dh58v61/dynamic_properties_accessorfuncs_and_more.py?dl=0

The latest is in my library, the link is in this text file at the very top...

Basically, it lets you do this:

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

the requirement of needing the class to exist first is meh - I am looking into an alternative, but I've already had issues if I create the Accessors during init even if the calls to those functions are always after init.... So, by doing it this way, I retain full dynamic ability with additional options and I can use the function form or property form..

Ie: self.p = 1234.03 is the same as self.SetP( 1234.03 ), and print( str( self.p ) ); is the same as print( str( self.GetP( ) ) ) or print( self.GetPToString( ) ), etc...

self.key == property self._key == the raw data stored, default is None - the default value is stored in the AccessorFunc object and the getter returns the default if the raw is none, if any only if the getter args don't alter the behavior to ignore defaults ( second arg )... The first arg for the getter is to override the default...

So self.GetP( ) where self._p == None and self.GetPDefaultValue( ) is 12.34 would return 12.34 and self.GetP( 24.56 ) would return 24.56 and self.GetP( 'does not matter', True ) would return None because of the ignore_defaults value ( ie if a value was set then it would return the value with the second arg set to True... but since no value is set, None is returned )...

There is also data-type and value protection so you can ensure a value being assigned is assigned iff ( If and only if ) the data-type and / or value is authorized... If it isn't, it is ignored.

This system adds a lot of freedom. I'll be adding a lot more features soon.

If you take a look at my response, etc.. .you'll see a larger list of features to come, and more... For instance: Auto setting up init functions, setting up the grouping system so if I Create a group ( All ) with p, y, r in that order... then do this.SetAll( p, y, r ); then all would be assigned.. I'll need to verify no naming collisions, but the goal is to reduce how much code is necessary.




回答3:


You might try something like

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

This will NOT check (or complain about) your dynamic properties, but at least it may be aware of non-dynamic inherited properties.



来源:https://stackoverflow.com/questions/42903048/python-dynamic-properties-and-mypy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!