A read-only data descriptor is a descriptor that defines both __get__ and __set__, but __set__ raises AttributeError when called. 
An example is a simple read-only property:
class Test():
    _i = 1
    @property
    def i(self):
        return self._i
assert hasattr(Test.i, '__get__')
assert hasattr(Test.i, '__set__')
t = Test()
t.i # 1
t.i = 2 # ERROR
If I have an instance of a class, I can determine if the instance attribute is a read-only data descriptor this way (although I don't like this at all):
def is_ro_data_descriptor_from_instance(instance, attr):
    temp = getattr(instance, attr)
    try:
        setattr(instance, attr, None)
    except AttributeError:
        return True
    else:
        setattr(instance, attr, temp)
        return False
If I know the class doesn't require any arguments to be instantiated, I can determine if its class attribute is a read-only data descriptor similar to the above:
def is_ro_data_descriptor_from_klass(klass, attr):
    try:
        setattr(klass(), attr, None)
    except AttributeError:
        return True
    else:
        return False
However, if I don't know the signature of the class ahead of time, and I try to instantiate a temporary object in this way, I could get an error:
class MyClass():
    i = 1
    def __init__(self, a, b, c):
        '''a, b, and c are required!'''
        pass
def is_ro_data_descriptor_from_klass(MyClass, 'i') # Error
What can be done to determine if a class attribute is a read-only data descriptor?
EDIT: Adding more information.
Below is the code I am trying to get working:
class StaticVarsMeta(type):
    '''A metaclass that will emulate the "static variable" behavior of
    other languages. For example: 
        class Test(metaclass = StaticVarsMeta):
            _i = 1
            @property
            def i(self):
                return self._i
        t = Test()
        assert t.i == Test.i'''
    statics = {}
    def __new__(meta, name, bases, dct):
        klass = super().__new__(meta, name, bases, dct)
        meta.statics[klass] = {}
        for key, value in dct.items():
            if "_" + key in dct:
                meta.statics[klass][key] = set()
                if hasattr(value, '__get__'):
                    meta.statics[klass][key].add('__get__')
                if hasattr(value, '__set__'):
                    try:
                        value.__set__(None, None)
                    except AttributeError:
                        continue
                    else:
                        meta.statics[klass][key].add('__set__')
        return klass
    def __getattribute__(klass, attr):
        if attr not in StaticVarsMeta.statics[klass]:
            return super().__getattribute__(attr)
        elif '__get__' not in StaticVarsMeta.statics[klass][attr]:
            return super().__getattribute__(attr)
        else:
            return getattr(klass, '_' + attr)
    def __setattr__(klass, attr, value):
        if attr not in StaticVarsMeta.statics[klass]:
            super().__setattr__(attr, value)
        elif '__set__' not in StaticVarsMeta.statics[klass][attr]:
            super().__setattr__(attr, value)
        else:
            setattr(klass, '_' + attr, value)
class Test(metaclass = StaticVarsMeta):
    _i = 1
    def get_i(self):
        return self._i
    i = property(get_i)
Note the following:
type(Test.i) # int
type(Test.__dict__['i']) # property
Test().i = 2 # ERROR, as expected
Test.i = 2 # NO ERROR - should produce an error
 
     
    