Over the past few weeks/months I have had on an off issues with the way python 
handles class-attributes. Consider the default way to do it:

```
class A:
    attr = 2
    """Docstring of the attribute"""
```

There are 2 main issues I have with this way of setting class-attributes:

1. It does not allow to set class-attributes that depend on other 
class-attributes. (Example: setting a path that depends on cls.__name__)
2. It does not allow to set lazily evaluated attributes. If setting the 
class-attribute involves an expensive computation, it will be done at class 
definition time.

One possible resolution is to use metaclasses, however this comes with its own 
issues:

1. MetaClasses do not work nice with documentation. Attributes defined my 
metaclasses will neither show up in help(), dir(), nor will they be in the 
correct place when documenting the class with tools like sphinx.
2. Who wants to write an additional metaclass whenever they want to write a 
class and have some minimal class-attribute processing?

A new possible resolution, possible  since python 3.9, is the combination of 
@classmethod and @property. However, this has it's own problems and pitfalls, 
cf.  https://bugs.python.org/issue44904, https://bugs.python.org/issue45356. In 
fact, after lots of trial and error and reading into descriptors I figured that 
"true" class-properties are not possible in the current versions of python for 
a very simple reason:

╔══════════════════════════════════════════════════════╗
║descriptor's __set__ method is never called when setting a class-attribute. 
(see MWE) ║
╚══════════════════════════════════════════════════════╝

Therefore, I propose the following:

1. By default `type.__setattr__ ` should check whether the attribute implements 
`__set__` and if so, use that instead. (it already works like this for 
object.__setattr__), (cf. PatchedTrig below)
2. There should be a clearer distinction between class-attributes and 
instances-attributes. Instances should probably by default not be allowed to 
change class-attributes, since this can affect global state.
3. It would be very nice to have a built-in @attribute decorator, much like 
@property, but specifically for stetting class attributes. (again, metaclasses 
are possible but the issues with documentation compatibility seem very bad) 
(pretty much what @classmethod@property promises but doesn't fulfill.)


## MWE

```python
import math

class TrigConst: 
    def __init__(self, const=math.pi):
        self.const = const
    
    def __get__(self, obj, objtype=None):
        return self.const
    
    def __set__(self, obj, value):
        print(f"__set__ called", f"{obj=}")
        self.const = value

class Trig:
    const = TrigConst()
```

Then 

```python
Trig().const                    # calls TrigConst.__get__
Trig().const = math.tau  # calls TrigConst.__set__
Trig.const                       # calls TrigConst.__get__
Trig.const = math.pi       # overwrites TrigConst attribute with float.
```

Patched version


```python
class PatchedSetattr(type):
    def __setattr__(cls, key, value):
        if hasattr(cls, key):
            obj = cls.__dict__[key]
            if hasattr(obj, "__set__"):
                obj.__set__(cls, value)
        else:
            super().__setattr__(key, value)

class PatchedTrig(metaclass=PatchedSetattr):
    const = TrigConst()
```

Then 

```python
cls = Trig
print(cls.const, type(cls.__dict__["const"]))
cls.const = math.tau
print(cls.const, type(cls.__dict__["const"]))
```

gives

```
3.141592653589793 <class '__main__.TrigConst'>
6.283185307179586 <class 'float'>
```

But

```cls
cls = PatchedTrig
print(cls.const, type(cls.__dict__["const"]))
cls.const = math.tau
print(cls.const, type(cls.__dict__["const"]))
```

gives

```
3.141592653589793 <class '__main__.TrigConst'>
__set__ called obj=<class '__main__.PatchedTrig'>
6.283185307179586 <class '__main__.TrigConst'>
```
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/SOGH3L4GFLIXNIU7CLY43RHMSUGFZURE/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to