Coroutine-based two-in-one properties
The proper way to prime a generator for accepting values is to send an initial None value.
Yield will simultaneously return a value to the caller, and accept a value sent from the caller.
Using those two facts, you could rewrite the code like:
...

def coroproperty(method: Callable) -&gt; property:

    ...

    def setter(self, value: Any) -&gt; None:
        coro = method(self)
        coro.send(None)
        with suppress(StopIteration):
            coro.send(value)

    ...

class Circle:

    ...

    @coroproperty
    def diameter(self):
        self.radius = (yield self.radius * 2) / 2

    ...

Side-effects
Wasted time and computation.  Setting the diameter, circumference and/or area first involves computing those quantities, which is unnecessary.
You could circumvent that by accepting a new value before returning the value.
    @coroproperty
    def diameter(self):
        diameter = yield None
        if diameter is not None:
           self.radius = diameter / 2
        else:
           yield self.radius * 2

The getter would need to call next(coro) twice, ignoring the first returned value: None.
If you desired a property which could store None, you'd need to create and use your own sentinel value.

Assignment to diameter, even if not changing the value, will change the radius.  In this example, it is changed from an int to a float:
&gt;&gt;&gt; c.radius = 100
&gt;&gt;&gt; c.radius
100
&gt;&gt;&gt; c.diameter = c.diameter
&gt;&gt;&gt; c.radius
100.0

Add in sqrt, and multiplication &amp; division by π, and accuracy can be lost:
&gt;&gt;&gt; c.radius = 1_000_000_000_000
&gt;&gt;&gt; c.area = c.area
&gt;&gt;&gt; c.radius
999999999999.9999