python-attrs / python-attrs/attrs

[RFC] A shortcut to define a classvar with the same name as an attribute

Open
#746 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Feature
Dominant language
Python
Stars
5.8k
Forks
480
Avg merge
2h 15m
Merged PRs (30d)
2

Description

Hello!

Thanks again for the great library! :)

After answering in a couple of issues regarding default values on class-level and debugging another mess with self-using factory which was fixed by migration to @cached_property, I thought that it may be good to have a simple way to set a class-level attribute with the same name as an attr.ib on attrs class.

The issue can be illustrated by a following example:

import attr

@attr.dataclass
class BaseClass:
    value: int
    magic: str

@attr.s
class FixedMagicClass(BaseClass):
    magic = attr.ib(init=False, default='alohomora')

@attr.s
class _ReprMagicClass(BaseClass):
    # A proxy class to exclude 'magic' from init
    magic = attr.ib(init=False)

@attr.s
class ReprMagicClass(_ReprMagicClass):
    @cached_property
    def magic(self):
        return repr(self.value)

I'd like to add classvar parameter in attr.ib and decorator syntactic sugar like this:

@attr.s
class FixedMagicClass(BaseClass):
    magic = attr.ib(init=False, classvar='alohomora')  # Almost no difference, but it can be accessed on the class object

@attr.s
class ReprMagicClass(BaseClass):
    @attr.ib(init=False).classvar
    @cached_property  # Property is a descriptor and is set on class object itself
    def magic(self):
        return repr(self.value)

Alternative approach may be implementing it via metadata + field_transformer, but syntax becomes clumsy. From my perspective this seems more like a core feature than like an extension.

Though, it may be a non-desired feature as it wouldn't work with slotted classes - they use their own slot descriptors - and therefore may complicate migration from dict-classes to slot-classes.

Other possible related feature, which may blend in perfectly with property and class-level constants, is a way to make an attr.ib skippable during __init__ - e.g. if an attribute is init=True and has default=attr.UNSET the generated __init__ code will be like following:

def __init__(self, required_attribute, attribute=attr.UNSET):
    self.required_attribute = required_attribute  #  This attribute had `attr.NOTHING` default in `attr.ib`
    if attribute is not attr.UNSET:  # If it wasn't set, we'll take the classvar instead
        self.attribute = attribute

I'll be happy to implement this proposal (actually - both of them) if it would be considered useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the examples in the issue and review the existing attr.ib and decorator behavior; no source files or tests are named. The first milestone is maintainer agreement on whether the classvar proposal, skippable-init proposal, or neither should be pursued, after which the chosen scope can be implemented and validated.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.