Last active
July 12, 2022 11:21
-
-
Save conqp/48e517dc926b77888d2fd52b9e28103f to your computer and use it in GitHub Desktop.
Coroutine-based two-in-one property.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
# coroproperty.py - Coroutine-based two-in-one properties. | |
# | |
# Copyright (C) 2020 Richard Neumann <mail at richard dash neumann period de> | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <https://www.gnu.org/licenses/>. | |
"""Coroutine-based two-in-one decorator.""" | |
from contextlib import suppress | |
from functools import wraps | |
from math import pi, sqrt | |
from time import perf_counter | |
from typing import Any, Callable, Union | |
def coroproperty(method: Callable) -> property: | |
"""Single decorator for getter and setter methods.""" | |
def getter(self) -> Any: | |
"""Property getter function.""" | |
coro = method(self) | |
value = next(coro) | |
coro.close() | |
return value | |
def setter(self, value: Any): | |
"""Property setter function.""" | |
coro = method(self) | |
next(coro) | |
try: | |
next(coro) | |
except StopIteration: | |
raise AttributeError(f"can't set attribute '{method.__name__}'") | |
with suppress(StopIteration): | |
coro.send(value) | |
return property(getter, setter) | |
class CoroCircle: | |
"""Information about a circle.""" | |
def __init__(self, radius: float): | |
"""Initializes the circle with its radius.""" | |
self.radius = radius | |
@coroproperty | |
def diameter(self): | |
"""Gets and sets the diameter.""" | |
yield self.radius * 2 | |
self.radius = (yield) / 2 | |
@coroproperty | |
def circumference(self): | |
"""Gets and sets the circumference.""" | |
yield self.diameter * pi | |
self.diameter = (yield) / pi | |
@coroproperty | |
def area(self): | |
"""Gets and sets the area.""" | |
yield pow(self.radius, 2) * pi | |
self.radius = sqrt((yield) / pi) | |
class ClassicCircle: | |
"""Information about a circle.""" | |
def __init__(self, radius: float): | |
"""Initializes the circle with its radius.""" | |
self.radius = radius | |
@property | |
def diameter(self): | |
"""Returns the diameter.""" | |
return self.radius * 2 | |
@diameter.setter | |
def diameter(self, diameter: float): | |
"""Sets the diameter.""" | |
self.radius = diameter / 2 | |
@property | |
def circumference(self): | |
"""Returns the circumference.""" | |
return self.diameter * pi | |
@circumference.setter | |
def circumference(self, circumference: float): | |
"""Sets the cirumference.""" | |
self.diameter = circumference / pi | |
@property | |
def area(self): | |
"""Returns the area.""" | |
return pow(self.radius, 2) * pi | |
@area.setter | |
def area(self, area: float): | |
"""Sets the area.""" | |
self.radius = sqrt(area / pi) | |
def timed(function: Callable[..., Any]) -> Callable[..., float]: | |
"""Times the execution of a function.""" | |
@wraps(function) | |
def wrapper(*args, **kwargs): | |
start = perf_counter() | |
function(*args, **kwargs) | |
return perf_counter() - start | |
return wrapper | |
def repeat(ntimes: int) -> Callable[[Callable[..., Any]], Callable[..., None]]: | |
"""Repeats a function.""" | |
def decorator(function: Callable[..., Any]) -> Callable[..., None]: | |
@wraps(function) | |
def wrapper(*args, **kwargs): | |
for _ in range(ntimes): | |
function(*args, **kwargs) | |
return wrapper | |
return decorator | |
@timed | |
@repeat(100_000) | |
def test_get(circle: Union[CoroCircle, ClassicCircle]) -> None: | |
"""Runs the getters repeatedly.""" | |
_ = circle.diameter | |
_ = circle.circumference | |
_ = circle.area | |
@timed | |
@repeat(100_000) | |
def test_set(circle: Union[CoroCircle, ClassicCircle], value: int) -> None: | |
"""Runs the setters repeatedly.""" | |
circle.diameter = value | |
circle.circumference = value | |
circle.area = value | |
def main(): | |
"""Benchmark.""" | |
coro_circle = CoroCircle(42) | |
classic_circle = ClassicCircle(42) | |
coro_time = test_get(coro_circle) | |
print('Coro get:', coro_time) | |
classic_time = test_get(classic_circle) | |
print('Classic get:', classic_time) | |
print('Ratio coro / classic:', coro_time / classic_time) | |
coro_time = test_set(coro_circle, 9000) | |
print('Coro set:', coro_time) | |
classic_time = test_set(classic_circle, 9000) | |
print('Classic set:', classic_time) | |
print('Ratio coro / classic:', coro_time / classic_time) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment