命題: https://x.com/pipichih/status/1709248514033909778
from functools import partial
from inspect import signature
from os.path import join
class PathDef:
@staticmethod
def scope(project):
return ScopedPathDef(project)
@staticmethod
def codebase():
return __file__.split('/root/')[0]
@staticmethod
def build_dir(project):
return join(PathDef.codebase(), 'build', project)
@staticmethod
def output_bin_dir(project, module):
return join(PathDef.build_dir(project), 'bin', module)
class ScopedPathDef: # PathHelper
def __init__(self, project):
self.project = project
def __getattr__(self, name):
attr = getattr(PathDef, name)
if isinstance(attr, str):
return attr
nargs = len(signature(attr).parameters)
return partial(attr, *(self.project,)[:nargs])
r"""
>>> PathDef.codebase = Path('codebase')
>>> PathDef.codebase
PosixPath('codebase')
>>> PathDef(scope='project').codebase
PosixPath('codebase')
>>> PathDef.build_dir(scope='project')
PosixPath('codebase/build/project')
>>> PathDef(scope='project').build_dir()
PosixPath('codebase/build/project')
>>> PathDef.output_bin_dir('module', scope='project')
PosixPath('codebase/build/project/bin/module')
>>> PathDef(scope='project').output_bin_dir('module')
PosixPath('codebase/build/project/bin/module')
TypeErrors:
>>> PathDef()
Traceback (most recent call last):
...
TypeError: __init__() missing 1 required keyword-only argument: 'scope'
>>> PathDef('project')
Traceback (most recent call last):
...
TypeError: __init__() takes 1 positional argument but 2 were given
>>> PathDef(scoping='project')
Traceback (most recent call last):
...
TypeError: __init__() got an unexpected keyword argument 'scoping'
>>> PathDef.output_bin_dir('module')
Traceback (most recent call last):
...
TypeError: output_bin_dir() missing 1 required keyword-only argument: 'scope'
>>> PathDef.output_bin_dir(scope='project')
Traceback (most recent call last):
...
TypeError: output_bin_dir() missing 1 required positional argument: 'module'
"""
from functools import wraps
from pathlib import Path
class PathDef:
codebase = Path(__file__.split('/root/')[0])
def __init__(self, *, scope):
self.project = scope
def scope(method):
@wraps(method)
def new_method(*a, **kw):
if a and isinstance(a[0], PathDef):
return method(*a, **kw)
else:
if 'scope' not in kw:
raise TypeError('%s() missing 1 required keyword-only argument: %r' % (method.__name__, 'scope'))
return method(PathDef(scope=kw.pop('scope')), *a, **kw)
return new_method
@scope
def build_dir(self):
return self.codebase / 'build' / self.project
@scope
def output_bin_dir(self, module):
return self.build_dir() / 'bin' / module
>>> def func(a, b, c='cc', *d, e=ee, **f): pass
...
>>> for member in dir(func.__code__): member, getattr(func.__code__, member)
...
('__class__', <class 'code'>)
...
('co_argcount', 3) # a, b, c which are positional
...
('co_kwonlyargcount', 1) # 'e' in this case
...
('co_nlocals', 6) # a, b, c, d, e, f which appears to be local variables
...
('co_varnames', ('a', 'b', 'c', 'e', 'd', 'f'))
...
>>> for member in dir(func): member, getattr(func, member)
...
('__defaults__', ('cc',))
...
('__kwdefaults__', {'e': 'ee'})
...
>>> from inspect import signature
>>> sig = signature(func)
>>> sig
<Signature (a, b, c='cc', *d, e='ee', **f)>
>>> for p in sig.parameters.values(): p, p.kind.description
...
(<Parameter "a">, 'positional or keyword')
(<Parameter "b">, 'positional or keyword')
(<Parameter "c='cc'">, 'positional or keyword')
(<Parameter "*d">, 'variadic positional')
(<Parameter "e='ee'">, 'keyword-only')
(<Parameter "**f">, 'variadic keyword')
- Introspecting callables with the Signature object: https://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
- Types and members: https://docs.python.org/3/library/inspect.html#types-and-members
- Code objects: https://docs.python.org/3.11/reference/datamodel.html#code-objects
from pathlib import Path
build_path = lambda codebase, project: Path(codebase, 'build', project)
build_bin_path = lambda codebase, project: build_path(codebase, project) / 'bin'
module_log_path = lambda codebase, project, module: build_path(codebase, project) / module / 'log'
assert build_path('codebase', 'project') == Path('codebase/build/project')
assert build_bin_path('codebase', 'project') == Path('codebase/build/project/bin')
assert module_log_path('codebase', 'project', 'module') == Path('codebase/build/project/module/log')
from os.path import join
class partial_func_obj:
def __init__(self, func, *args):
self.func = func
self.args = tuple(arg for arg in args if arg)
self.nargs = len(args)
def __call__(self, *args):
acc_args = (
tuple(arg for arg in (self.args + args) if arg) +
(None,) * self.nargs
)[:self.nargs]
return partial(self.func, *acc_args)
def partial(func, *args):
if None not in args:
return func(*args)
return partial_func_obj(func, *args)
def build_dir(codebase, project):
return join(codebase, 'build', project)
def build_bin_dir(codebase, project):
return join(build_dir(codebase, project), 'bin')
def module_log_dir(codebase, project, module):
return join(build_dir(codebase, project), module, 'log')
assert build_dir('codebase', 'project') == 'codebase/build/project'
assert build_bin_dir('codebase', 'project') == 'codebase/build/project/bin'
assert module_log_dir('codebase', 'project', 'module') == 'codebase/build/project/module/log'
assert isinstance(partial(build_dir, None, None), partial_func_obj)
assert isinstance(partial(build_dir, 'codebase', None), partial_func_obj)
assert partial(build_dir, 'codebase', 'project') == 'codebase/build/project'
assert partial(module_log_dir, 'codebase', None, None)('project', 'module') == 'codebase/build/project/module/log'
assert partial(module_log_dir, 'codebase', None, None)('project')('module') == 'codebase/build/project/module/log'