python / python/cpython

Add public functions to dataclasses to correctly identify methods and features

Offen
#157,314 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

stdlib topic-dataclasses type-feature
Vorherrschende Sprache
Python
Sterne
77.2k
Forks
35.9k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Feature or enhancement

Proposal:

There are a few places both in the stdlib and in third party packages where tools attempt to discover specific features of how dataclasses are constructed. Currently these either rely on leaky internals or will behave incorrectly in certain cases, or both.

I'd like to both make it possible to stop leaking those internal details while also making the discovery more accurate.

Proposal:

  1. Add a __generated_for_dataclass__ attribute to methods generated by dataclasses
    • This would hold the class the method was generated for
    • Note that this would not include shared functions like __replace__ which are attached to dataclasses but are not generated for a specific class
    • Adding a True/False flag was proposed back in the issue that added pprint repr replacement support, this extends that idea to also identify which class a method was generated for which gives more useful information[^1]
  2. Add a public generated_for_dataclass[^2] function to retrieve this attribute
    • This would return None if the method is not a generated dataclass method
  3. Add an is_frozen public function to detect if a class is actually a frozen dataclass
    • The current implementation would check that generated_for_dataclass(cls.method) is cls for both __setattr__ and __delattr__
    • This is both a convenience function and future-proofing if the implementation of frozen-ness changes

In the linked discuss thread I also mentioned adding a requires_decorator to is_dataclass to identify if a class has been decorated and doesn't just inherit from a dataclass along with making _is_dataclass_instance public and adding a matching is_dataclass_type. These additions could be used to simplify some existing checks but aren't necessary to resolve the logic issues. As such I'm leaving them out unless it's considered worthwhile to include them.


Examples of issues this intends to resolve:

  1. Attempts to detect and replace a dataclass __repr__:
  • pprint
    • This relies on dataclasses not cleanly renaming the __repr__ function and leaking the name of the internal __create_fn__ function through the recursive_repr wrapper
    • This prevented dataclasses from renaming the internal function in a previous PR
    • A change that would no longer leak the internal name would also break this (I discovered this while testing changes for lazy method generation)
  • enum
    • This only checks if repr=True is in the params and doesn't check if the __repr__ method is actually generated by dataclasses and will replace any __repr__ method on a dataclass
    • As a dataclass, defining the __repr__ by hand should be treated the same as setting repr=False but this isn't the case for enum
  • rich
    • This checks for reprlib.py's path in __code__.co_filename
    • Any __repr__ function on a dataclass that uses recursive_repr will be replaced
    • Unlike the 2 stdlib cases, this can't rely on being kept in sync with changes to dataclasses due to failing stdlib tests

These could all use generated_for_dataclass(cls.__repr__) to both identify if a __repr__ is replaceable and get the correct class to use for retrieving the matching fields.


  1. Current attempts to detect frozenness:

There are a number of tools that look at cls.__dataclass_params__.frozen to try and detect if a class is a frozen dataclass, here's an example from pytorch and other examples from a search.

This logic will fail if __setattr__ or __delattr__ have been replaced in an undecorated subclass.

from dataclasses import dataclass, is_dataclass

@dataclass(frozen=True)
class Frozen:
    a: int = 42

class UnFrozen(Frozen):
    __setattr__ = object.__setattr__
    __delattr__ = object.__delattr__

ex = UnFrozen()
print(is_dataclass(ex))  # True
print(ex.__dataclass_params__.frozen)  # True
ex.a = 1  # Not actually frozen

[^1]: For example if you want to replace the __repr__ function, it tells you which class to use for fields(cls) to generate the correct method
[^2]: Open to a different name if there's a better suggestion

Has this already been discussed elsewhere?

I have already discussed this feature proposal on Discourse

Links to previous discussion of this feature:

https://discuss.python.org/t/add-public-functions-to-inspect-dataclass-parameters-and-methods/108613/12

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Überprüfe die Implementierung von dataclasses und die Erkennungslogik in Lib/pprint.py und Lib/enum.py und vergleiche sie anschließend mit den Beispielen des Vorschlags aus Tools von Drittanbietern. Ermittle, wie generierte Methoden und tatsächlich eingefrorene Klassen identifiziert werden sollten, einschließlich der Benennung und des Gültigkeitsbereichs der öffentlichen APIs. Als erledigt gilt die Aufgabe, wenn die vereinbarten APIs und Attribute implementiert sind und die beschriebenen Fälle mit Vererbung und handgeschriebenen Methoden abgedeckt werden.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
developer-experience
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Aktiv
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
45/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.