How to add property to a class dynamically?

前端 未结 24 1897
梦毁少年i
梦毁少年i 2020-11-22 12:44

The goal is to create a mock class which behaves like a db resultset.

So for example, if a database query returns, using a dict expression, {\'ab\':100, \'cd\'

相关标签:
24条回答
  • 2020-11-22 13:27

    Only way to dynamically attach a property is to create a new class and its instance with your new property.

    class Holder: p = property(lambda x: vs[i], self.fn_readonly)
    setattr(self, k, Holder().p)
    
    0 讨论(0)
  • 2020-11-22 13:30

    This seems to work(but see below):

    class data(dict,object):
        def __init__(self,*args,**argd):
            dict.__init__(self,*args,**argd)
            self.__dict__.update(self)
        def __setattr__(self,name,value):
            raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
        def __delattr__(self,name):
            raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
    

    If you need more complex behavior, feel free to edit your answer.

    edit

    The following would probably be more memory-efficient for large datasets:

    class data(dict,object):
        def __init__(self,*args,**argd):
            dict.__init__(self,*args,**argd)
        def __getattr__(self,name):
            return self[name]
        def __setattr__(self,name,value):
            raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
        def __delattr__(self,name):
            raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
    
    0 讨论(0)
  • 2020-11-22 13:31

    I suppose I should expand this answer, now that I'm older and wiser and know what's going on. Better late than never.

    You can add a property to a class dynamically. But that's the catch: you have to add it to the class.

    >>> class Foo(object):
    ...     pass
    ... 
    >>> foo = Foo()
    >>> foo.a = 3
    >>> Foo.b = property(lambda self: self.a + 1)
    >>> foo.b
    4
    

    A property is actually a simple implementation of a thing called a descriptor. It's an object that provides custom handling for a given attribute, on a given class. Kinda like a way to factor a huge if tree out of __getattribute__.

    When I ask for foo.b in the example above, Python sees that the b defined on the class implements the descriptor protocol—which just means it's an object with a __get__, __set__, or __delete__ method. The descriptor claims responsibility for handling that attribute, so Python calls Foo.b.__get__(foo, Foo), and the return value is passed back to you as the value of the attribute. In the case of property, each of these methods just calls the fget, fset, or fdel you passed to the property constructor.

    Descriptors are really Python's way of exposing the plumbing of its entire OO implementation. In fact, there's another type of descriptor even more common than property.

    >>> class Foo(object):
    ...     def bar(self):
    ...         pass
    ... 
    >>> Foo().bar
    <bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
    >>> Foo().bar.__get__
    <method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>
    

    The humble method is just another kind of descriptor. Its __get__ tacks on the calling instance as the first argument; in effect, it does this:

    def __get__(self, instance, owner):
        return functools.partial(self.function, instance)
    

    Anyway, I suspect this is why descriptors only work on classes: they're a formalization of the stuff that powers classes in the first place. They're even the exception to the rule: you can obviously assign descriptors to a class, and classes are themselves instances of type! In fact, trying to read Foo.bar still calls property.__get__; it's just idiomatic for descriptors to return themselves when accessed as class attributes.

    I think it's pretty cool that virtually all of Python's OO system can be expressed in Python. :)

    Oh, and I wrote a wordy blog post about descriptors a while back if you're interested.

    0 讨论(0)
  • 2020-11-22 13:31

    I recently ran into a similar problem, the solution that I came up with uses __getattr__ and __setattr__ for the properties that I want it to handle, everything else gets passed on to the originals.

    class C(object):
        def __init__(self, properties):
            self.existing = "Still Here"
            self.properties = properties
    
        def __getattr__(self, name):
            if "properties" in self.__dict__ and name in self.properties:
                return self.properties[name] # Or call a function, etc
            return self.__dict__[name]
    
        def __setattr__(self, name, value):
            if "properties" in self.__dict__ and name in self.properties:
                self.properties[name] = value
            else:
                self.__dict__[name] = value
    
    if __name__ == "__main__":
        my_properties = {'a':1, 'b':2, 'c':3}
        c = C(my_properties)
        assert c.a == 1
        assert c.existing == "Still Here"
        c.b = 10
        assert c.properties['b'] == 10
    
    0 讨论(0)
  • 2020-11-22 13:32

    I asked a similary question on this Stack Overflow post to create a class factory which created simple types. The outcome was this answer which had a working version of the class factory. Here is a snippet of the answer:

    def Struct(*args, **kwargs):
        def init(self, *iargs, **ikwargs):
            for k,v in kwargs.items():
                setattr(self, k, v)
            for i in range(len(iargs)):
                setattr(self, args[i], iargs[i])
            for k,v in ikwargs.items():
                setattr(self, k, v)
    
        name = kwargs.pop("name", "MyStruct")
        kwargs.update(dict((k, None) for k in args))
        return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})
    
    >>> Person = Struct('fname', 'age')
    >>> person1 = Person('Kevin', 25)
    >>> person2 = Person(age=42, fname='Terry')
    >>> person1.age += 10
    >>> person2.age -= 10
    >>> person1.fname, person1.age, person2.fname, person2.age
    ('Kevin', 35, 'Terry', 32)
    >>>
    

    You could use some variation of this to create default values which is your goal (there is also an answer in that question which deals with this).

    0 讨论(0)
  • 2020-11-22 13:33

    The goal is to create a mock class which behaves like a db resultset.

    So what you want is a dictionary where you can spell a['b'] as a.b?

    That's easy:

    class atdict(dict):
        __getattr__= dict.__getitem__
        __setattr__= dict.__setitem__
        __delattr__= dict.__delitem__
    
    0 讨论(0)
提交回复
热议问题