typing
typing copied to clipboard
Monkey patching support
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)
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.
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.
# 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.