Skip to content

Instantly share code, notes, and snippets.

@samuelcolvin
Last active April 27, 2022 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samuelcolvin/aae8eec5c145de73e8781a26cf56b533 to your computer and use it in GitHub Desktop.
Save samuelcolvin/aae8eec5c145de73e8781a26cf56b533 to your computer and use it in GitHub Desktop.

Differentiating between "fields" and library methods/attributes

(Samuel Colvin)

See this discussion.




We've all seen the problem:

# in a library or the std library...

class BaseModel:  # could be a decorator, same potential problem
    def __init__(self, **kwargs):
      magic(self, kwargs)

    fields: list[FieldInfo] = ...

    def json(self):
        """return a JSON representation of the object"""


# in user code...

class Request(BaseModel):
    method: str
    path: str
    fields: Optional[MultiDict]  # form data - ERROR!!!
    json: Optional[dict]  # object from decoding body - ERROR!!!

Affects:

  • dataclasses
  • namedtuple
  • validation/parsing libraries like attrs, pydantic, marshmallow, etc.
  • ORMs like sqlalchemy, django, etc.



Potential solutions/workarounds

(in order rough order of how easy they are now)

use module level methods

e.g. mylibrary.json(model)

This is currently used by: attrs, dataclasses ...

Disadvantage:

  • less ergonomic
  • harder for IDE to help
  • steeper learning curve
  • IMHO ugly



Use one underscore

e.g. model._json()

Currently used by NamedTuple.

Disadvantage:

  • encourages use of _ private methods
  • pycharm complains
  • hard to distinguish between these methods and really private methods



Use dunder methods

e.g. model.__json__()

Used a bit by pydantic.

Disadvantage:

  • often looks "even more private" to new users
  • again introduces bad habits



Use a namespace attribute

e.g. model.object.json()

Used (sort of) by django model_instance.objects.whatever.

Disadvantage:

  • harder to add new methods as a user
  • slower
  • again IDE might not be able to help



User dunder methods, add public methods to access them

Suggested by Guido - as with thing.__len__() and len(thing).

Also __replace__ used by replace(model) was suggested.

Disadvantage:

  • This might work for common methods like __len__ and __fields__, but not all methods
  • Same problems as "use module level methods" above



Use use __getattr__

e.g. model['json'] to get the field/value, then model.json() to access the function.

Disadvantage: breaks the idea of fields as attributes, more typing, unintuitive.




use a common prefix for methods

e.g. model.model_json().

Then prevent that prefix model_ in fields.




Allow a new character at the start of identifiers

e.g. model.$json()

Disadvantage: everyone else seems to hate this idea.

This is not the big change it might seem like: lots of exotic unicode characters are already allowed in identifiers.

This is used by javascript (for jquery).

e.g. model.ᐅjson() actually "works", though using it is a terrible idea in reality.




Add a new "accessor"

e.g. model::json()

Most concise and (arguably) clear solution.

Disadvantage:

  • bigger change
  • Makes the language more complicated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment