pygx.patching¶
Systematic patching on symbolic values.
patching
¶
Systematic patching on symbolic values.
While pygx.Symbolic.sym_rebind
already provides a flexible programming interface for modifying symbolic
values, this module builds on it to:
-
Provide user-friendly helpers for the most common patching patterns, such as patching by key, path, type, value or class member.
-
Package related changes into reusable, composable semantic groups (
pygx.patching.Patcher), which can also be created from URI-like strings so patching can be driven from the command line.
Patcher
¶
Patcher(
*args: Any,
root_path: KeyPath | None = None,
override_args: bool = False,
ignore_extra_args: bool = False,
**kwargs: Any
)
Bases: Functor
Class that patches a symbolic value by returning a rebind dict.
To accommodate reusable patching, we introduced the concept of patcher,
which is a symbolic function that takes an input value with a list of
optional arguments and produces a patching rule (see
pygx.patch) or a tuple of (patching_rule,
validation_rule). A validation rule is a callable object that validates
the patched object's integrity if it's being patched by others later. A
patcher can be created from a URI-like string, to better serve the
command-line interface.
A patcher can be created via the pg.patcher decorator, returning a
rebind dict:
@pg.patcher([
('lr', pg.typing.Float(min_value=0.0))
])
def learning_rate(trainer, lr):
return {
'training.learning_rate': lr
}
or a rebind function:
@pg.patcher([
('lr', pg.typing.Float(min_value=0.0))
])
def learning_rate(trainer, lr):
def _rebind_fn(k, v, p):
if k and k.key == 'learning_rate' and isinstance(v, float):
return lr
return v
return _rebind_fn
or a composition of rules:
@pg.patcher([
('lr', pg.typing.Float(min_value=0.0)),
('weight_decay', pg.typing.Float(min_value=0.0))
])
def complex(trainer, lr, weight_decay):
# `change_lr` and `change_weight_decay` are other patcher classes.
# Their arguments must be passed by keyword: the first positional
# argument of a patcher is reserved for the patching target.
return [
change_lr(lr=lr),
change_weight_decay(weight_decay=weight_decay)
]
After registration, a patcher object can be obtained from a URI-like string:
Then the user can use the patcher to patch an object:
A list of patchers can be applied sequentially to accommodate combination
of semantic groups. A patcher in the sequence can propose updates to the
original value or generate a replacement for the original value.
pg.patching.patch is introduced for making it convenient to chain
multiple patchers using URI-like strings:
The user can look up all registered patchers via:
Source code in pygx/symbolic/_functor.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
patch
¶
patch(x: Symbolic) -> Any
Patches an input and returns the input itself unless replaced.
Calls the patcher body with x to obtain the patching rule (and
optional validation rule), then applies the rule via
pygx.patch. Returns a different object only
when a rule replaces the whole value.
Source code in pygx/patching/_rule_based.py
validate
¶
validate(x: Symbolic) -> None
Validates an input's integrity.
This method will be called in pygx.patch
after a chain of patchers has been applied, to validate that the
patched object still conforms to this patcher's plan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Symbolic
|
The input after modification. |
required |
Source code in pygx/patching/_rule_based.py
ObjectFactory
¶
ObjectFactory(
value_type: type[Symbolic],
base_value: Symbolic | Callable[[], Symbolic] | str,
patches: PatchType | None = None,
params_override: dict[str, Any] | str | None = None,
) -> Any
A factory to create a symbolic object from a base value and patches.
ObjectFactory is a functor: calling it with arguments returns a
factory object, and calling that object produces the patched value.
Example:
class Trainer(pg.Object):
lr: float = 0.1
steps: int = 100
factory = pg.patching.ObjectFactory(
Trainer, Trainer(), {'lr': 0.01})
trainer = factory()
assert trainer.lr == 0.01
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_type
|
type[Symbolic]
|
Type of the return value. |
required |
base_value
|
Symbolic | Callable[[], Symbolic] | str
|
An instance of |
required |
patches
|
PatchType | None
|
Optional patching rules. See
|
None
|
params_override
|
dict[str, Any] | str | None
|
A rebind dict (or a JSON string as a serialized rebind dict) as an additional patch to the value. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
Value after applying |
Any
|
|
Source code in pygx/patching/_object_factory.py
from_maybe_serialized
¶
Loads a value from a maybe-serialized form (JSON file or JSON string).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any | str
|
Source of the value. It can be the value (non-string type)
itself, or a file path (a string ending with |
required |
value_type
|
type[Any] | None
|
An optional type to constrain the value. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
Value from source. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the loaded value is not an instance of |
Source code in pygx/patching/_object_factory.py
patch_on_key
¶
patch_on_key(
src: Symbolic,
regex: str,
value: Any = MISSING_VALUE,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values whose keys match a regular expression.
Every node in the symbolic tree is tested - containers included - so a
matching key replaces the entire value stored under it. The key is the
last component of a node's path (e.g. 'b' for path 'a.b').
Example:
d = pg.Dict(a=0, b=2)
pg.patching.patch_on_key(d, 'a', value=3)
# d is now {a=3, b=2}.
pg.patching.patch_on_key(d, '.', value=4)
# d is now {a=4, b=4}.
class A(pg.Object):
x: int
def on_sym_post_init(self):
super().on_sym_post_init()
self._num_changes = 0
def on_sym_change(self, updates):
super().on_sym_change(updates)
self._num_changes += 1
a = A(x=1)
pg.patching.patch_on_key(a, 'x', value=2)
# a._num_changes is 1.
pg.patching.patch_on_key(a, 'x', value=3)
# a._num_changes is 2.
pg.patching.patch_on_key(a, 'x', value=4, skip_notification=True)
# a._num_changes is still 2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
Symbolic value to patch. |
required |
regex
|
str
|
Regex for the key name, matched from the beginning of the name
(via |
required |
value
|
Any
|
New value for the fields whose keys match |
MISSING_VALUE
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces a new value based on the old
value. Mutually exclusive with |
None
|
skip_notification
|
bool | None
|
If True, the |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_member
¶
patch_on_member(
src: Symbolic,
cls: type[Any] | tuple[type[Any], ...],
name: str,
value: Any = MISSING_VALUE,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values that are the requested member of classes.
Example:
class A(pg.Object):
x: int
d = pg.Dict(a=A(x=1), b=2)
pg.patching.patch_on_member(d, A, 'x', 2)
# d is now {a=A(x=2), b=2}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
Symbolic value to patch. |
required |
cls
|
type[Any] | tuple[type[Any], ...]
|
The class (or classes) the member belongs to. |
required |
name
|
str
|
Member name. |
required |
value
|
Any
|
New value for the matched members.
An explicit |
MISSING_VALUE
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces a new value based on the old
value. Mutually exclusive with |
None
|
skip_notification
|
bool | None
|
If True, the |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_path
¶
patch_on_path(
src: Symbolic,
regex: str,
value: Any = MISSING_VALUE,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched paths.
Example:
d = pg.Dict(a={'x': 1}, b=2)
pg.patching.patch_on_path(d, '.*x', value=3)
# d is now {a={x=3}, b=2}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
Symbolic value to patch. |
required |
regex
|
str
|
Regex for the key path (e.g. |
required |
value
|
Any
|
New value for the fields whose paths match |
MISSING_VALUE
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces a new value based on the old
value. Mutually exclusive with |
None
|
skip_notification
|
bool | None
|
If True, the |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_type
¶
patch_on_type(
src: Symbolic,
value_type: type[Any] | tuple[type[Any], ...],
value: Any = MISSING_VALUE,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched types.
Example:
d = pg.Dict(a={'x': 1}, b=2)
pg.patching.patch_on_type(d, int, value_fn=lambda x: x * 2)
# d is now {a={x=2}, b=4}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
Symbolic value to patch. |
required |
value_type
|
type[Any] | tuple[type[Any], ...]
|
Value type to match. |
required |
value
|
Any
|
New value for the fields whose values are instances of
|
MISSING_VALUE
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces a new value based on the old
value. Mutually exclusive with |
None
|
skip_notification
|
bool | None
|
If True, the |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
patch_on_value
¶
patch_on_value(
src: Symbolic,
old_value: Any,
value: Any = MISSING_VALUE,
value_fn: Callable[[Any], Any] | None = None,
skip_notification: bool | None = None,
) -> Any
Recursively patch values on matched values.
Example:
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Symbolic
|
Symbolic value to patch. |
required |
old_value
|
Any
|
Old value to match. |
required |
value
|
Any
|
New value for the fields whose values equal |
MISSING_VALUE
|
value_fn
|
Callable[[Any], Any] | None
|
Callable object that produces a new value based on the old
value. Mutually exclusive with |
None
|
skip_notification
|
bool | None
|
If True, the |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/patching/_pattern_based.py
allow_repeated_patcher_registration
¶
Sets whether registering an already-registered patcher name is allowed.
Allowed by default; a repeated registration replaces the previous
patcher. When disallowed, registering a duplicate name raises
KeyError.
Source code in pygx/patching/_rule_based.py
from_uri
¶
from_uri(uri: str) -> Patcher
Creates a Patcher object from a URI-like string.
The format is <patcher_name>?<arg1>&<arg2>=<val2>: the name of a
registered patcher, optionally followed by positional and keyword
arguments as strings, which are parsed/validated against the patcher's
value specs. The returned patcher is unbound from any target; apply it
with its patch() method.
Example:
@pg.patching.patcher([('x', pg.typing.Int())])
def increment(v, x=1):
return lambda k, v, p: v + x if isinstance(v, int) else v
p = pg.patching.from_uri('increment?x=3')
assert p.patch(pg.Dict(a=1)) == {'a': 4}
Raises:
| Type | Description |
|---|---|
KeyError
|
If the patcher name is not registered. |
ValueError
|
If the URI is malformed or arguments fail to parse. |
Source code in pygx/patching/_rule_based.py
patch
¶
patch(value: Symbolic, rule: PatchType) -> Any
Apply patches to a symbolic value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Symbolic
|
A symbolic value to patch. |
required |
rule
|
PatchType
|
A patching rule is one of the following:
1) A dict of symbolic paths to the new values.
2) A rebind function defined by signature (k, v) or (k, v, p).
See |
required |
Returns:
| Type | Description |
|---|---|
Any
|
Value after applying the patchers. If any patcher returned a new value |
Any
|
(by returning a single-item dict containing '' as its key), the return |
Any
|
value will be a different object than |
Any
|
will be returned after applying the patches. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Error if the patch name and arguments cannot be parsed successfully. |
Source code in pygx/patching/_rule_based.py
patcher
¶
patcher(
args: list[tuple[str, ValueSpec]] | None = None,
name: str | None = None,
auto_typing: bool = False,
auto_doc: bool = False,
) -> Any
Decorate a function into a Patcher and register it.
A patcher function is defined as:
The signature takes at least one argument as the patching target, with additional arguments as patching parameters to control the details of this patch.
Example:
import pygx as pg
@pg.patching.patcher([
('x', pg.typing.Int())
])
def increment(v, x=1):
return lambda k, v, p: v + x if isinstance(v, int) else v
d = pg.Dict(a=1)
pg.patching.patch(d, [increment(x=2)]) # d is now {a=3}.
# The same rule via a URI-like string. d is now {a=5}.
pg.patching.patch(d, ['increment?x=2'])
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
list[tuple[str, ValueSpec]] | None
|
A list of (arg_name, arg_value_spec) to schematize patcher arguments. |
None
|
name
|
str | None
|
String to be used as the patcher name in URIs. If None, the function name will be used as the patcher name. |
None
|
auto_typing
|
bool
|
If True, automatically infer the typing from the function signature. |
False
|
auto_doc
|
bool
|
If True, use the docstring of the function as the docstring of the patcher. |
False
|
Returns:
| Type | Description |
|---|---|
Any
|
A decorator that converts a function into a Patcher subclass. |
Source code in pygx/patching/_rule_based.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2