How to add property to a class dynamically?

前端 未结 24 1915
梦毁少年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:19

    The best way to achieve is by defining __slots__. That way your instances can't have new attributes.

    ks = ['ab', 'cd']
    vs = [12, 34]
    
    class C(dict):
        __slots__ = []
        def __init__(self, ks, vs): self.update(zip(ks, vs))
        def __getattr__(self, key): return self[key]
    
    if __name__ == "__main__":
        c = C(ks, vs)
        print c.ab
    

    That prints 12

        c.ab = 33
    

    That gives: AttributeError: 'C' object has no attribute 'ab'

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

    It seems you could solve this problem much more simply with a namedtuple, since you know the entire list of fields ahead of time.

    from collections import namedtuple
    
    Foo = namedtuple('Foo', ['bar', 'quux'])
    
    foo = Foo(bar=13, quux=74)
    print foo.bar, foo.quux
    
    foo2 = Foo()  # error
    

    If you absolutely need to write your own setter, you'll have to do the metaprogramming at the class level; property() doesn't work on instances.

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

    You don't need to use a property for that. Just override __setattr__ to make them read only.

    class C(object):
        def __init__(self, keys, values):
            for (key, value) in zip(keys, values):
                self.__dict__[key] = value
    
        def __setattr__(self, name, value):
            raise Exception("It is read only!")
    

    Tada.

    >>> c = C('abc', [1,2,3])
    >>> c.a
    1
    >>> c.b
    2
    >>> c.c
    3
    >>> c.d
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'C' object has no attribute 'd'
    >>> c.d = 42
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 6, in __setattr__
    Exception: It is read only!
    >>> c.a = 'blah'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 6, in __setattr__
    Exception: It is read only!
    
    0 讨论(0)
  • 2020-11-22 13:22

    Here is a solution that:

    • Allows specifying property names as strings, so they can come from some outside data source instead of all being listed in your program.
    • Adds the properties when the class is defined, instead of every time an object is created.

    After the class has been defined, you just do this to add a property to it dynamically:

    setattr(SomeClass, 'propertyName', property(getter, setter))
    

    Here is a complete example, tested in Python 3:

    #!/usr/bin/env python3
    
    class Foo():
      pass
    
    def get_x(self):
      return 3
    
    def set_x(self, value):
      print("set x on %s to %d" % (self, value))
    
    setattr(Foo, 'x', property(get_x, set_x))
    
    foo1 = Foo()
    foo1.x = 12
    print(foo1.x)
    
    0 讨论(0)
  • 2020-11-22 13:23

    For those coming from search engines, here are the two things I was looking for when talking about dynamic properties:

    class Foo:
        def __init__(self):
            # we can dynamically have access to the properties dict using __dict__
            self.__dict__['foo'] = 'bar'
    
    assert Foo().foo == 'bar'
    
    
    # or we can use __getattr__ and __setattr__ to execute code on set/get
    class Bar:
        def __init__(self):
            self._data = {}
        def __getattr__(self, key):
            return self._data[key]
        def __setattr__(self, key, value):
            self._data[key] = value
    
    bar = Bar()
    bar.foo = 'bar'
    assert bar.foo == 'bar'
    

    __dict__ is good if you want to put dynamically created properties. __getattr__ is good to only do something when the value is needed, like query a database. The set/get combo is good to simplify the access to data stored in the class (like in the example above).

    If you only want one dynamic property, have a look at the property() built-in function.

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

    You cannot add a new property() to an instance at runtime, because properties are data descriptors. Instead you must dynamically create a new class, or overload __getattribute__ in order to process data descriptors on instances.

    0 讨论(0)
提交回复
热议问题