未提供项目描述
项目描述
包装纸
一组Python装饰器和实用工具,用于抽象常见的/繁琐的Python模式。
说明
这个包更侧重于提供有趣的抽象,并尝试拓展Python代码组织的可能性。我并不认为使用这些函数会提供“好”的代码,我也不会支持用它们来创建恶意的Python代码 ;)。
这里的一些函数可能会使代码更难以理解,但这是可以的。我想给它们留出空间存在,并希望我们可以进一步发展它们,使它们更容易理解,并提供更直观和熟悉的抽象。
这个包是关于实验,并试图在避开Python纯主义者(以及可能有更多理性的人)的审查的同时,创建基本的、有趣的、自然感觉的、方便的抽象。我想看看这个语言的其他有趣的结构。
所以,我想这个包的座右铭是:自由发展,但负责任地使用。<3
需要注意的是,其中一些功能可能与linters不兼容 😢
简单示例
import wrappingpaper as wp
@wp.contextdecorator
def doing_something(a, b):
print(a)
yield
print(b)
# por que no los dos?
# you can do this
with doing_something(4, 5):
print(1)
# prints 4 1 5
# as well as this
@doing_something(4, 5)
def something():
print(1)
something()
# prints 4 1 5
包括
- 辅助模块
- 伪导入的实现,作为提供的导入机制类的案例研究
- 日志记录/错误处理
- 捕获函数抛出的错误,并将其重定向到记录器
- 上下文管理器
- 同时作为函数包装器的上下文管理器
- 对象属性
- 类和实例缓存
- 动态属性对象 - 为属性提供嵌套属性和方法!!
- 函数签名助手
- 覆盖并更新函数签名
- 过滤函数方案之外的函数参数
- 实际更新包装器的partial
- 导入机制
- 创建伪模块并自定义模块的导入方式(感谢上帝,我处理了一些复杂的问题)
- 可迭代对象
- 包含我从其他项目中提取的一些基本可迭代函数,这样我就不必在各个地方重复它们。
- 杂项
- 我还没整理的东西。你知道的?
- 异常时重试
- 检查循环引用
安装
pip install wrappingpaper
用法
import wrappingpaper as wp
辅助模块
这些是模拟模块,利用 wrappingpaper
的 导入机制 来修改从它们导入的模块。
lazyimport
这是使用定义的导入机制实现的简单懒加载导入。
from lazyimport import sklearn
import librosa # sklearn imports will be lazy
预置
这是对 bmcfee/presets 的重新实现,它包括导入机制,而不是在之后包装模块。我可能会给那个包添加一个 PR,但在这里实现它目前很简单,我觉得它并不重要到足以通过审查流程。
from presets import librosa
librosa.update(sr=44100) # now functions will default to sr=44100
日志记录
注意:我还没有投入精力来模拟日志对象进行测试,因此请注意,在当前形式下,它们未经测试,并且很可能存在 1 或 2 个错误。
我正在处理一个错误抑制和日志记录的项目。会有函数被 try-except 块包装,日志调用,以及大量冗余的框架。
所以我做了工作来提取这些内容,并在装饰器中执行许多常见模式。
这里的日志装饰器主要用于允许失败并返回默认/空值而不使整个程序崩溃的函数。
它还包括从跟踪信息中提取信息的实用程序。我还没有处理日志处理器和格式化器,所以这是一个待办事项。
import logging
log = logging.getLogger(__name__)
# handle and log error
@wp.log_error_as_warning(log, default=dict)
def get_stats(x=None):
if x is True:
raise ValueError() # some error happens
return {'a': 5, 'b': 6}
assert get_stats() == {'a': 5, 'b': 6}
assert get_stats(True) == {}
大致相当于
def get_stats(x=None):
try:
if x is True:
raise ValueError() # some error happens
return {'a': 5, 'b': 6}
except ValueError as e:
log.warning('Exception in get_stats: %s', e)
return {}
上下文管理器
Python 中的两种常见模式是上下文管理器和装饰器。它们通常具有相同的基本结构:进行一些初始化,运行一个函数,并进行一些清理。
在不同的上下文中,它们都可以用于提供干净的代码,但为了使用它们,我经常发现自己围绕上下文管理器编写一个额外的包装函数,然后你必须给它一个稍微不同的名称,这可能会让人困惑。
所以,出现了 contextdecorator
,它与 contextlib.contextmanager
的工作方式相同,但也可以作为函数装饰器。当用作装饰器时,它将在上下文管理器内调用函数。
@wp.contextdecorator
def doing_something(a, b):
print(a)
yield
print(b)
# por que no los dos?
# you can do this
with doing_something(4, 5):
print(1)
# as well as this
@doing_something(4, 5)
def something():
print(1)
something()
有时,你的装饰器并不那么简单,你需要在装饰器中进行一些不同的操作(例如,你需要包装函数的名称)。
@doing_something.caller # override default decorator
def doing_something(func, a, b): # wrapped function, decorator arguments
# change arguments
name = func.__name__
a = 'calling {}: {}'.format(name, a)
b = 'calling {}: {}'.format(name, b)
# return the wrapped function
@functools.wraps(func)
def inner(*args, **kw):
with doing_something(a, b):
return func(*args, **kw)
return inner
大致相当于
import functools
from contextlib import contextmanager
@contextmanager
def doing_something(a, b):
print(a)
yield
print(b)
def doing_something2(a, b):
def outer(func):
@functools.wraps(func)
def inner(*a, **kw):
with doing_something(a, b):
return func(*a, **kw)
return inner
return outer
# used like:
with doing_something(4, 5):
print(1)
@doing_something2(4, 5)
def something():
print(1)
something()
属性
Python 属性对象非常有用,因为它们允许你创建具有复杂功能的自然感觉对象,并且所有这些都捆绑在一个非常好的界面中。
但是使用它们时,我经常发现自己多次在实用文件中编写相同的类。
一个用例是缓存。你可以提供不同级别的缓存。
cachedproperty
:在实例对象上缓存 - 每个实例运行一次onceproperty
:在类对象上缓存 - 每个类/基类运行一次overridable_property
:作为正常属性工作(调用包装函数),直到属性被分配。然后它返回分配的值。overridable_method
:作为正常方法工作(调用包装函数),直到函数作为装饰器被调用。然后它调用包装函数。在实例级别上工作。
import time
class SomeClass:
@wp.cachedproperty
def instance_prop(self):
'''This is run once per object instance.'''
return time.time()
@wp.onceproperty
def class_prop(self):
'''This is run once. It is cached in the property
object itself.'''
return time.time()
@wp.overridable_property
def overridable(self):
'''This property is run normally, until another value is assigned on top.'''
return time.time()
def __init__(self, overridable=None):
if overridable: # override the property value
# stores at self._overridable
self.overridable = overridable
# otherwise it just uses the property function like usual
a = SomeClass()
b = SomeClass()
assert a.instance_prop != b.instance_prop # prop runs once per object
assert a.class_prop == b.class_prop # prop runs only once
assert a.overridable != a.overridable # gets called twice, shouldn't be the same
a.overridable = 5
assert a.overridable == 5 # now the value is overridden
assert SomeClass(5).overridable == 5 # overriding inside class
函数签名
这是我一直在寻找的东西。
我个人喜欢将大量函数参数打包到文件中的配置文件的想法。
我也讨厌在 5 层嵌套函数调用中重复传递参数。
我喜欢将关键字参数(**kw
)传递给下一个函数。
但是存在一些情况,您的关键词字典中有额外的配置值,而您只想传递您函数需要接收的值。
# dynamic function defaults
@wp.configfunction
def asdf(a=5, b=6, c=7):
return a + b + c
assert asdf() == 5+6+7 # normal behavior
asdf.update(a=1)
assert asdf() == 1+6+7 # updated default
assert asdf(3) == 3+6+7 # automatically resolves kwargs and posargs
asdf.clear()
assert asdf() == 5+6+7 # back to normal behavior
# filter out kwargs not in the signature (if **kw, it's a no-op).
@wp.filterkw
def asdf(a=5, b=6, c=7):
return a + b + c
assert asdf(b=10, d=1234) == 5+10+7
对象
Monkeypatching
class Blah:
def asdf(self):
return 10
b = Blah()
@wp.monkeypatch(b)
def asdf():
return 11
assert asdf() == 11
asdf.reset() # remove patch
assert asdf() == 10
asdf.repatch() # re-place the patch
assert asdf() == 11
命名空间
class something(metaclass=wp.namespace):
one_thing = 5
other_thing = 6
def blah(x):
return one_thing + other_thing + x
assert something.blah(10) == 5+6+10
可迭代对象
#####################
# loop breaking
#####################
items = wp.until(x if x != 7 else wp.done for x in range(10))
assert list(items) == list(range(0, 6))
####################
# loop throttling
####################
# make sure that a for loop doesn't go too fast.
# limit the time one iteration takes.
t0 = time.time()
for x in wp.throttled(range(10), 1):
print(x)
assert time.time() - t0 > 10
# limiting the number of iterations to 10.
# with no iterable passed, it loops infinitely and
# yields the total yield time and the time it had to sleep.
for dt, time_asleep in wp.limit(wp.throttled(secs=1), 10):
print('Iteration took {}s. Had to sleep for {}s.'.format(dt, time_asleep))
print('-'*10)
################################
# Use `while True:` in a loop
################################
for _ in wp.infinite():
print('this is gonna be a while...')
#########################
# pre-check an iterable
#########################
# check the first n items in an iterable, without removing them.
it = iter(range(6))
items, it = wp.pre_check_iter(it, 3)
assert items == [0, 1, 2]
assert list(it) == [0, 1, 2, 3, 4, 5, 6]
###########################################
# repeat and chain a function infinitely
###########################################
import random
def get_numbers(): # function returns an iterable
return [random.random() for _ in range(10)]
numbers = wp.run_iter_forever(get_numbers)
# repeat get_numbers() and chain iterable outputs together
all_numbers = list(wp.limit(numbers, 100))
assert all(isinstance(x, float) for x in all_numbers)
# If no items are returned by a call, instead of the iterable hanging
# indefinitely waiting for an item, return None.
def get_numbers():
if random.random() > 0.8: # make random breaks
return # returns empty
return [random.random() for _ in range(10)]
numbers = wp.run_iter_forever(get_numbers, none_if_empty=True)
# this SHOULD contain sporadic None's at a multiple of 10
all_numbers = list(wp.limit(numbers, 5000))
assert None in all_numbers
导入机制
这可能是在这里玩耍最危险的事情。
Python公开了其许多内部机制,包括其导入系统。
因此,我们可以利用这一点来提供修改模块行为的导入包装器。
基本示例 - 懒加载
# lazyimport/__init__.py
import wrappingpaper as wp
wp.lazy_loader.activate(__name__)
# main.py
from lazyimport import sklearn.model_selection
# sklearn is not currently loaded
sklearn.model_selection.train_test_split() # now it's loaded.
修改已从您的伪模块导入的模块
import wrappingpaper as wp
@wp.PseudoImportFinder.modulemodifier
def my_loader(module):
module.sneakything = '......hi'
my_loader.activate('somethingrandom')
# now somewhere else, you can do
from somethingrandom import numpy as np
assert np.sneakything == '......hi'
包装模块以修改模块内容
import importlib
import wrappingpaper as wp
# create the module wrapper that will traverse and modify the module when it is loaded.
class Module(wp.ModuleWrapper):
# this is called for each item in the module
def _wrapattr(self, attr, value):
# do whatever you want with the value
if callable(value) and getattr(value, '__doc__', None) is not None:
value.__doc__ += '\nI was here.'
elif self._is_submodule(value):
value = Module(value)
# always pass attr and modified value to be set,
# otherwise it will be undefined.
super()._wrapattr(attr, value)
# applies the module wrapper on load
@wp.PseudoImportFinder.moduleloader
def my_loader(spec):
return Module(importlib.util.module_from_spec(spec))
# somewhere else (or in the same place. I'm not ur mom), actually use it
with my_loader.activated('somethingrandom'): # activated only inside context
from somethingrandom import glob
print(glob.glob.__doc__)
assert glob.glob.__doc__.endswith('I was here.')
杂项
一些尚未整理的其他杂项内容。
import random
# retry a function if an exception is raised
@wp.retry_on_failure(10)
def asdf():
x = random.random()
if x < 0.5:
raise ValueError
return x
# will either return a number that is definitely > 0.5
# or every number in the first 10 tries were below 0.5
try:
assert asdf() > 0.5
except ValueError:
print("Couldn't get a number :/")
# ignore error
with wp.ignore():
a, b = 5, 0
c = a / b # throws divide by zero
a = 10 # never run
assert a == 5
项目详情
wrappingpaper-0.0.6.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6180e25b8a31ff7507c73e8c77b9aeb086c678e4ab47a6e8456f3352670da717 |
|
MD5 | 00792f53d0e3dea84cf798a2773b38ed |
|
BLAKE2b-256 | d4092fce8a3a49a9750513d68ce9a39e8115e5b92f736bf307174a07caac754e |