开发者

Can descriptors be used on properties to provide some declarative information?

开发者 https://www.devze.com 2023-03-27 07:13 出处:网络
I\'m new to Python so forgive me if I\'m not even using the right terminology...I\'m using Python 3.2 and I\'m trying to figure out whether I can decorate a class property with some declarative-style

I'm new to Python so forgive me if I'm not even using the right terminology... I'm using Python 3.2 and I'm trying to figure out whether I can decorate a class property with some declarative-style information.

In my mind i开发者_运维百科t would look like this:

class MyTestClass:

    def __init__(self, foo):
        self.foo = foo

    @property
    @somedeclarativeInfo("ABC",123)
    def radius(self):
        return self.__foo

    @radius.setter
    def radius(self, foo):
        self.__foo = foo

There are then two different things I'd want to do with the class:

A - Be able to interact with the foo property just like any other property (simple gets and sets)

B - Be able to dynamically find properties on a particular class that are decorated with this descriptor and be able to pull out the "ABC" and 123 values, etc.

I think maybe I should be creating a descriptor to accomplish what I want, but I'm not sure if I'm on the right track, or if this can be done.

Since my background is .Net I whipped up the following example to show what I want to do, in case that helps anyone understand my goal:

using System;
using System.Reflection;

namespace SampleWithProperties
{
    public class MyCustomAttribute : Attribute
    {
        public string Val1;
        public string Val2;

        public MyCustomAttribute(string val1,string val2)
        {
            Val2 = val2;
            Val1 = val1;
        }
    }

    public class Foo
    {
        [MyCustomAttribute("abc","def")]
        public string PropertyA { get; set; }

        [MyCustomAttribute("xyz","X")]
        public int PropertyB { get; set; }

        public string PropertyC { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Show that we can figure out which properties have the custom attribute,
            // and that we can get the values for Val1 and Val2

            foreach(PropertyInfo propertyInfo in typeof(Foo).GetProperties())
            {
                Console.WriteLine("Found a property named "+propertyInfo.Name);

                foreach(Attribute attribute in propertyInfo.GetCustomAttributes(
                    attributeType:typeof(MyCustomAttribute),inherit:true))
                {
                    Console.WriteLine("Found a MyCustomAttribute on the property.");

                    MyCustomAttribute myCustomAttribute = attribute as MyCustomAttribute;

                    Console.WriteLine("Val1 = " + myCustomAttribute.Val1);
                    Console.WriteLine("Val2 = " + myCustomAttribute.Val2);
                }

                Console.WriteLine();
            }

            // Show that the properties can be used like normal

            Foo foo = new Foo {PropertyA = "X", PropertyB = 2, PropertyC = "Z"};
            Console.WriteLine("Created an instance of Foo just for fun.  Its property values are "+
                foo.PropertyA+","+foo.PropertyB+","+foo.PropertyC);
        }
    }
}

Can this be done?


There is no simple way to do what you want with properties. You can't simply set attributes on or get attributes from items protected by a property.

def declarativeInfo(*args, **kwargs):
    def wrapper(obj):
        for arg in args:
            setattr(obj, arg, arg)
        for k, v in kwargs:
            setattr(obj, k, v)
        return obj
    return wrapper

class MyTestClass:

    def __init__(self, foo):
        print MyTestClass.__dict__
        self.radius = self.Radius('foo')

    @declarativeInfo(bar="ABC",baz=123)
    class Radius(object):
        def __init__(self, foo):
            self.value = foo

a = MyTestClass('foo')

print a.radius.value
print a.radius.a

is the easiest way to do this. You can always, of course, make value a property.

If you really want radius to be a normal property, you can store the information elsewhere in a dict and retrieve it from self.propdict or something.


OK, I wrote this question when I was first getting started with Python. I now know how to do in Python exactly what the .Net sample code I posted did. Granted, the biggest thing I didn't realize when I originally posted the question was that descriptors alter the behavior of your attributes/properties(whatever you call them). Nonetheless, we can still allow these attributes to act like properties (and not change their behavior) yet put some metadata on them with the decorator. I'm currently implementing some protocol serialization/deserialization stuff where this is going to come in handy.

class MyCustomDescriptor:

    def __init__(self,val1,val2):d
        self._val1 = val1
        self._val2 = val2

    @property
    def val1(self): return self._val1

    @property
    def val2(self): return self._val2

    def __call__(self,decorated_method_reference):
        self._decorated_method_reference = decorated_method_reference
        return self

    def __get__(self,instance,type=None):

        if not instance:
            return self

        return self._decorated_method_reference(instance)

class Foo:

    def __init__(self,attribute_a_value,attribute_b_value,attribute_c_value):
        self._attribute_a_value = attribute_a_value
        self._attribute_b_value = attribute_b_value
        self._attribute_c_value = attribute_c_value

    @MyCustomDescriptor(val1="abc",val2="def")
    def attribute_a(self): return self._attribute_a_value

    @MyCustomDescriptor(val1="xyz",val2="X")
    def attribute_b(self): return self._attribute_b_value

    @property
    def attribute_c(self): return self._attribute_c_value

# Show that by inspecting class Foo we can figure out which attribute are marked with MyCustomDescriptor and that
# we can get the values for val1 and val2.  We don't even need an instance of Foo to do this.  The class itself is sufficient.

print("Inspecting class Foo.  Looking for attributes marked with MyCustomDescriptor...")

for attribute_name in dir(Foo):

    attribute_as_object = getattr(Foo,attribute_name)

    if type(attribute_as_object) == MyCustomDescriptor:
        print("attribute "+attribute_name+" is decorated with MyCustomDescriptor.  val1="+attribute_as_object.val1+" val2="+attribute_as_object.val2)

# Show that the properties on Foo work like normal properties.  Note that I skipped implementing setters but could have done so.

foo_instance = Foo(attribute_a_value="X",attribute_b_value=2,attribute_c_value="Z")

print("Created an instance of Foo just for fun.  It's property values are "+str(foo_instance.attribute_a)+", "+str(foo_instance.attribute_b)+", "+str(foo_instance.attribute_c))

The output is:

Inspecting class Foo.  
Looking for attributes marked with MyCustomDescriptor...
attribute attribute_a is decorated with MyCustomDescriptor.  
val1=abc val2=def
attribute attribute_b is decorated with MyCustomDescriptor.  
val1=xyz val2=X
Created an instance of Foo just for fun.  
It's property values are X, 2, Z
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号