python / typing

Python static typing home. Hosts the documentation and a user help forum.
https://typing.readthedocs.io/
Other
1.57k stars 231 forks source link

Monkey patching support #718

Open srittau opened 4 years ago

srittau commented 4 years ago

These are just some not terribly well though out ideas about possible monkey support in typing. This would need a proper PEP if it was ever implemented, but I just want to record my ideas here:

# foo accepts a date object, but allows to add and access
# unknown attributes at runtime. It basically treats date
# as having __getattr__()/__setattr__() methods.
def foo(date: Dynamic[datetime.date]) -> None:
    date.my_attr = 123
    print(date.added_by_called)

# typing.dynamic() is a no-op at runtime and basically an alias
# for cast(Generic[X], y) at type-check time, where X is the
# type of y.
# In this example, dt has type Dynamic[datetime.date].
dt = dynamic(datetime.date.today())
foo(dt)

# The following would also work:
foo(datetime.date.today())
other_dt: datetime.date = dt

A possible extensions would be to define the allowed monkey-patched attributes. Something like:

class Monkey(Protocol):
    donkey: int

def foo(dt: Dynamic[datetime.date, Monkey]) -> None:
    # ok:
    print(dt.year)
    dt.donkey += 3
    # errors:
    print(dt.kong)
    dt.donkey += ""

dt = dynamic(datetime.date.today(), Monkey)
srittau commented 4 years ago

One open question is: What does foo: Dynamic[X, Y] mean? Does is mean that foo is guaranteed to already have the attributes defined by Y or only that it allows those attributes to be set? The former would make x = dynamic(datetime.date.today(), Y) not work, the latter would compromise type safety.

JukkaL commented 4 years ago

Dynamic[X, Y] sounds similar to "unsafe unions" that have been discussed previously. X would be compatible with Dynamic[X, Y], and vice versa. Dynamic[X] could potentially be represented as an unsafe union of X and a type with suitable __getattr__ and __setattr__ methods.

bluetech commented 4 years ago
# The following would also work:
foo(datetime.date.today())
other_dt: datetime.date = dt

The two lines combine to mean that X is both a subtype and a supertype of Dynamic[X], isn't it a bit strange? I would have expected the first line not to work without a dynamic(...).


Dynamic[X, Y] sounds similar to "unsafe unions" that have been discussed previously.

Is a Union is the right concept for this? A monkeypatching can happen on top of another monkeypatching and override with a different type, which makes it non-commutative.

Maybe Augmented[X, Y], Augmented[X, Y, Z] == Augmented[Augmented[X, Y], Z]?


BTW, we've had a somewhat related use case in pytest, where un-type-safe monkey-patching was used extensively, and came up with this solution: https://github.com/pytest-dev/pytest/blob/5.4.2/src/_pytest/store.py. However it requires the cooperation of the source type to expose a "Store", so doesn't handle arbitrary augmentations of 3rd-party types.