Skip to content

Instantly share code, notes, and snippets.

@conqp
Last active July 12, 2022 11:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save conqp/48e517dc926b77888d2fd52b9e28103f to your computer and use it in GitHub Desktop.
Save conqp/48e517dc926b77888d2fd52b9e28103f to your computer and use it in GitHub Desktop.
Coroutine-based two-in-one property.
#! /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