Coroutine Decorators

Code-Stücke können hier veröffentlicht werden.
Antworten
Schard
User
Beiträge: 7
Registriert: Freitag 25. Dezember 2020, 01:23
Wohnort: Hannover

Freitag 25. Dezember 2020, 01:32

Moin zusammen,

da ich mich über die Feiertage irgendwie beschäftigen muss, habe ich heute ein wenig Unsinn mit Python getrieben.
Herausgekommen ist dabei u.a. Coroutine-basierter Properties, welche Getter und Setter in einer Methode vereinheitlichen.
Der Code unten ist auch als Gist verfügbar.

Code: Alles auswählen

#! /usr/bin/env python3
#  lsimports.py - List imported modules of Python projects.
#
#  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 typing import Callable


def coroproperty(method: Callable) -> property:
    """Single decorator for getter and setter methods."""

    @wraps(method)
    def getter(self):
        """Property setter function."""
        coro = method(self)
        value = next(coro)
        coro.close()
        return value

    @wraps(method)
    def setter(self, value):
        """Property getter function."""
        coro = method(self)
        next(coro)
        next(coro)

        with suppress(StopIteration):
            coro.send(value)

    return property(getter, setter)


class Circle:
    """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)


if __name__ == '__main__':
    circle = Circle(42)
Benutzeravatar
__blackjack__
User
Beiträge: 8430
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Freitag 25. Dezember 2020, 11:27

@Schard: Das ein Setzen gleichzeitig eine unnötige Abfrage zur Folge hat ist IMHO unschön weil total überraschend und kann auch problematisch sein wenn dabei unerwünschte Seiteneffekte entstehen.
“For every complex problem, there is a solution that is simple, neat, and wrong.” — H. L. Mencken
Schard
User
Beiträge: 7
Registriert: Freitag 25. Dezember 2020, 01:23
Wohnort: Hannover

Freitag 25. Dezember 2020, 13:48

Wie gesagt: Unsinn. Es ist mehr so ein Proof-of-concept, dass man einen Getter und Setter in einer Methode vereinheitlichen kann.
Vorteil: Weniger Code
Nachteile: Kompliziert zu lesen, unnötige Abfragen, höllisch langsam¹.

[1]

Code: Alles auswählen

Coro get: 0.2394869327545166
Classic get: 0.09408378601074219
Ratio coro / classic: 2.5454644515174247
Coro set: 0.7087152004241943
Classic set: 0.10921454429626465
Ratio coro / classic: 6.489201644257868
Benutzeravatar
kbr
User
Beiträge: 1229
Registriert: Mittwoch 15. Oktober 2008, 09:27

Freitag 25. Dezember 2020, 13:54

@Schard: angenommen das Auslesen eines berechneten Attributs wäre teuer, dann hätte das Setzen (möglicherweise auch teuer) zusätzlich noch diesen Overhead – unbeschadet von möglichen Seiteneffekten, auf die __backjack__ bereits hingewiesen hat. Was Du da machst, ist zu "tricky". Lieber etwas mehr Code schreiben und diesen dafür einfach halten.
Schard
User
Beiträge: 7
Registriert: Freitag 25. Dezember 2020, 01:23
Wohnort: Hannover

Freitag 25. Dezember 2020, 14:02

Ich stimme euch beiden zu. Der o.g. Code ist nicht für den produktiven Einsatz gedacht, sondern um zu zeigen, dass die Grundidee möglich ist.
Bezüglich des Overheads; das Auslesen sollte nicht teuer sein:
PEP8 hat geschrieben:Note 3: Avoid using properties for computationally expensive operations; the attribute notation makes the caller believe that access is (relatively) cheap.
narpfel
User
Beiträge: 360
Registriert: Freitag 20. Oktober 2017, 16:10

Freitag 25. Dezember 2020, 22:38

@Schard: (Wahrscheinlich schlechte) Idee :): Nimm das Codeobjekt von deinem Generator-Property und baue zwei verschiedene Funktionen daraus, einmal den Getter und einmal den Setter. Schließlich ist kein Unsinn so unsinnig, dass man ihn mit mehr Unsinn nicht noch unsinniger machen kann.
Antworten