Generally, the best approach I found is overriding __ilshift__ as a setter and __rlshift__ as a getter, being duplicated by the property decorator.
It is almost the last operator being resolved just (| & ^) and logical are lower.
It is rarely used (__lrshift__ is less, but it can be taken to account).
Within using of PyPi assign package only forward assignment can be controlled, so actual 'strength' of the operator is lower.
PyPi assign package example:
class Test:
    def __init__(self, val, name):
        self._val = val
        self._name = name
        self.named = False
    def __assign__(self, other):
        if hasattr(other, 'val'):
            other = other.val
        self.set(other)
        return self
    def __rassign__(self, other):
        return self.get()
    def set(self, val):
        self._val = val
    def get(self):
        if self.named:
            return self._name
        return self._val
    @property
    def val(self):
        return self._val
x = Test(1, 'x')
y = Test(2, 'y')
print('x.val =', x.val)
print('y.val =', y.val)
x = y
print('x.val =', x.val)
z: int = None
z = x
print('z =', z)
x = 3
y = x
print('y.val =', y.val)
y.val = 4
output:
x.val = 1
y.val = 2
x.val = 2
z = <__main__.Test object at 0x0000029209DFD978>
Traceback (most recent call last):
  File "E:\packages\pyksp\pyksp\compiler2\simple_test2.py", line 44, in <module>
    print('y.val =', y.val)
AttributeError: 'int' object has no attribute 'val'
The same with shift:
class Test:
    def __init__(self, val, name):
        self._val = val
        self._name = name
        self.named = False
    def __ilshift__(self, other):
        if hasattr(other, 'val'):
            other = other.val
        self.set(other)
        return self
    def __rlshift__(self, other):
        return self.get()
    def set(self, val):
        self._val = val
    def get(self):
        if self.named:
            return self._name
        return self._val
    @property
    def val(self):
        return self._val
x = Test(1, 'x')
y = Test(2, 'y')
print('x.val =', x.val)
print('y.val =', y.val)
x <<= y
print('x.val =', x.val)
z: int = None
z <<= x
print('z =', z)
x <<= 3
y <<= x
print('y.val =', y.val)
y.val = 4
output:
x.val = 1
y.val = 2
x.val = 2
z = 2
y.val = 3
Traceback (most recent call last):
  File "E:\packages\pyksp\pyksp\compiler2\simple_test.py", line 45, in <module>
    y.val = 4
AttributeError: can't set attribute
So <<= operator within getting value at a property is the much more visually clean solution and it is not attempting user to make some reflective mistakes like:
var1.val = 1
var2.val = 2
# if we have to check type of input
var1.val = var2
# but it could be accendently typed worse,
# skipping the type-check:
var1.val = var2.val
# or much more worse:
somevar = var1 + var2
var1 += var2
# sic!
var1 = var2