Suggesting for overloading the assign operator

R

Rim

Hi,

I have been thinking about how to overload the assign operation '='.
In many cases, I wanted to provide users of my packages a natural
interface to the extended built-in types I created for them, but the
assign operator is always forcing them to "type cast" or coerce the
result when they do a simple assign for the purpose of setting the
value of a variable. Borrowing an example from this newgroup, the
second assignment below ereases all Currency knowledge from variable
'c', when the user only wanted to update the value of c, not its type:

c=Currency(5)
c=7

I fully understand the workaround of doing c=Curreny(7) instead, but
users don't want to be bothered with that.

I read some of the prior discussions, but I did not see a suggestion
for permanent a solution. I would like to propose the following.

The idea is to separate the value assignment from the type assignment,
by introducing a new operator, let's say the ':=' operator.

'=' remains what it is, assigns type and value at the same time
':=' assigns only the value

For example:
6

In reality, := would just coerce the return type of the RHS of := to
the type of the variable on the LHS of the :=, preserving the value(s)
as best as it can.

If the type of the variable was still undefined, ':=' would behave
like '='. It might create confusion in future code, but does not break
old code.

I think it would be a nice feature to have. Comments?

Thanks,
- Rim
 
T

Terry Reedy

Rim said:
Hi,

I have been thinking about how to overload the assign operation '='.

Assignment is a statement, not an operator, and therefore not
overloadable . This is an intentional design decision that GvR will
not change.

TJR
 
S

Steven Taschuk

Quoth Rim:
[...]
The idea is to separate the value assignment from the type assignment,
by introducing a new operator, let's say the ':=' operator.

'=' remains what it is, assigns type and value at the same time
':=' assigns only the value

That's a peculiar way of speaking. '=' does not assign type and
value to a variable; it assigns an object to a name.

[...]
In reality, := would just coerce the return type of the RHS of := to
the type of the variable on the LHS of the :=, preserving the value(s)
as best as it can.

"The type of the variable" is not a meaningful phrase in Python:
names do not have types; objects do. I assume you meant "the type
of the object the name on the lhs is presently bound to".

Coerce how? Do you want a new __magicmethod__ for this operation?

Would ':=' mutate the object presently bound to the lhs name
create a new object and rebind the lhs name? To be concrete:

a = whatever
b = a
a := 7
assert a is b

Should that assertion succeed or fail, in general? (Note that it
fails in general if the ':=' is replaced with '=', even if
whatever == 7.)

Would ':=' support chaining? That is, would
a := b := <some expression>
work? If so, would it be equivalent to
_tmp = <some expression>
a := _tmp
b := _tmp
(except for the namespace pollution), to match the semantics of
chained '='? (Note that the assignments happen left to right.)

Would use of a name on the lhs of a ':=' cause that name to be
considered local, as such use with '=' does?
If the type of the variable was still undefined, ':=' would behave
like '='. It might create confusion in future code, but does not break
old code.

Yikes. Why not raise NameError if the name on the lhs of ':=' is
not yet bound?
I think it would be a nice feature to have. Comments?

Your example
a = Currency(6)
a := 7
is highly strange. (Even if we ignore the question of *which*
currency is being represented; the example could easily be
restated with, say, FixedPoint or Rational or some such.)

How is it that your users are happy to write Currency(...) when
first assigning to a name, but not happy doing so when assigning
later?

Do they not understand that '=' binds a name to an object? Are
they C programmers who think of assignment as copying a value from
one location in memory to another? Do they think of the first
assignment as a type declaration for the name? (If any of these
three are true, they need to be better educated -- they're all
fundamental misconceptions about Python.)

Do they need a language with syntactic support for Currency
objects?
 
J

John Roth

Rim said:
Hi,

I have been thinking about how to overload the assign operation '='.
In many cases, I wanted to provide users of my packages a natural
interface to the extended built-in types I created for them, but the
assign operator is always forcing them to "type cast" or coerce the
result when they do a simple assign for the purpose of setting the
value of a variable.

Use a property. Changing the semantics of the assignment
statement (NOT assignment operator) is not going to happen.

Properties are new in Python 2.2 (which is around a year
old by now.) They enable you to have getter and setter
methods with an interface that looks like an instance
variable.

John Roth
 
R

Rim

Terry Reedy said:
Assignment is a statement, not an operator, and therefore not
overloadable . This is an intentional design decision that GvR will
not change.

Well, what about the idea of providing another statement, the one I
proposed in the original post?

'=' statement assigns type and value
':=' statement assigns value only

Rim
 
A

Alan Kennedy

Rim said:
What do you think about providing a statement to assign values
without assigning type?

'=' statement assigns value and type
':=' statement assigns value ony

I think the concept has some merit. I think that sometimes it useful to ensure
that the target of a rebind operation have the same type as the object which was
bound before the rebind.

I know that the same effect can be achieved by descriptors or overriding
"setattr" et al. Or something like this

a = 9
b = 42
if isinstance(b, type(a)):
a = b
else:
raise TypeError("Incompatible types.")

However, these options, i.e. putting type checking logic in "setattr" et al, do
not make it explicit in the code that type enforcement is in place.

Expressing the enforcement syntactically, as Rim suggests, would make it more
explicit for the programmer, as well as providing some extremely useful
optimisation hints, especially for products such as psyco.

Psyco, if I understand it correctly, generates machine code for all paths
through a suite, depending on the types of the objects manipulated in the suite.
Such explicitly stated type enforcement would provide valuable information to
optimisers.

Not that I'm proposing that the OPs solution be adopted. Just that I think he is
addressing a valid concern.

regards,
 
A

Aahz

I have been thinking about how to overload the assign operation '='.
In many cases, I wanted to provide users of my packages a natural
interface to the extended built-in types I created for them, but the
assign operator is always forcing them to "type cast" or coerce the
result when they do a simple assign for the purpose of setting the
value of a variable. Borrowing an example from this newgroup, the
second assignment below ereases all Currency knowledge from variable
'c', when the user only wanted to update the value of c, not its type:

c=Currency(5)
c=7

I fully understand the workaround of doing c=Curreny(7) instead, but
users don't want to be bothered with that.

If you have users who don't want to learn Python, give them a special
Python-like language. I won't say it's trivial, but it's not that hard.
 
J

John Roth

John Roth said:
Use a property. Changing the semantics of the assignment
statement (NOT assignment operator) is not going to happen.

Properties are new in Python 2.2 (which is around a year
old by now.) They enable you to have getter and setter
methods with an interface that looks like an instance
variable.

John Roth

I usually don't reply to my own posts, but I thought it
might be nice if I put up a skeletal example of how to do it.

I'm assuming that what you want is a way of insuring, in a
class named Foo, that the variable fooBar is always bound
to a class instance of Bar, regardless of what the user tries
to assign to it.

Most of what follows is documented in "What's New in Python 2.2,"
section 2: Peps 252 and 253: Type and Class Changes.

The information on the __new__ method is in the document
under 2.5: Related Links.

The way to do this is:

1. Both Foo and Bar have to inherit from a built-in type.
That's usually "object", but it can be any built-in type.

2. Refactor Foo so that all references to self.fooBar
now refer to self._fooBar. (It can be any name; the
underscore is a convention that means "private."

3. Insert the following code in Foo:

def _getFooBar(self):
return self._fooBar

def _setFooBar(self, parm):
self._fooBar = Bar(parm)

_fooBar = property(_setFooBar, _getFoobar, None, "doc strings swing")

4. In class Bar, use the __new__ method to check if
the parameter is an instance of class Bar. If it is, return that
instance, otherwise call your superclass's __new__ method
to create a new object and return it.

5. In class Bar, do whatever type determination you need to
in the __init__ method to build your new Bar object.

Most of this is documented in "What's New in Python 2.2,"
section 2: Peps 252 and 253: Type and Class Changes.

The information on the __new__ method is in the document
under 2.5: Related Links.

HTH

John Roth
 
C

Cliff Wells

The idea is to separate the value assignment from the type assignment,
by introducing a new operator, let's say the ':=' operator.

'=' remains what it is, assigns type and value at the same time
':=' assigns only the value

On the surface, this idea seems appealing. However, besides the
arguments that others have forwarded, you might consider the fact that
given two similar "assignment operators", subtle bugs are bound to
arise.

What if the programmer accidentally uses '=' rather than ':=' (and I
think this would be a natural mistake)? It would be legal Python but
would probably not have the intended effect. It is not unlike the
"allow assignment in expressions" arguments that come up here every 3.2
weeks.

Anyway, your arguments cancel themselves out. You asked initially for a
way to overload '=' because your users didn't want to have to type
something different. But ':=' *is* different, so what are you really
looking for?. Worse, because ':=' isn't *very* different, it makes
mistakes more likely.

In short, I think using a = myint(6) or even a.value = 6 is preferable.
Besides, when I used Pascal (many years ago), I found ':=' to be one of
the most annoying (and most common) things I had to type. Please don't
ask to bring it back.


Regards,
 
C

Cliff Wells

Not that I'm proposing that the OPs solution be adopted. Just that I think he is
addressing a valid concern.

I agree that it is a valid concern. I think, however, that other valid
concerns outweigh this one. I suspect you think so as well, otherwise
you would have omitted the first sentence.

Regards,
 
I

Ian Bicking

I think the concept has some merit. I think that sometimes it useful to ensure
that the target of a rebind operation have the same type as the object which was
bound before the rebind.

I know that the same effect can be achieved by descriptors or overriding
"setattr" et al. Or something like this

Or maybe:

class stricttype(object):

counter = 1

def __init__(self, required_type, doc=None, real_attr=None):
self.required_type = required_type
if not real_attr:
real_attr = '_stricttype_attr_%i' % stricttype.counter
stricttype.counter += 1
self.real_attr = real_attr
self.doc = doc

def __set__(self, obj, value):
if not isinstance(value, self.required_type):
raise TypeError, 'must be of type %r' % self.required_type
setattr(obj, self.real_attr, value)

def __get__(self, obj, cls):
return getattr(obj, self.real_attr)

def __del__(self, obj):
delattr(obj, self.real_attr)

def __doc__(self, obj):
return self.doc


class IntPoint(object):

def __init__(self, x, y):
self.x = x
self.y = y

def __str__(self):
return 'Point: (%i, %i)' % (self.x, self.y)

x = stricttype(int)
y = stricttype(int)
Traceback (most recent call last):
File "stricttype.py", line 44, in ?
p.x = 2.2
File "stricttype.py", line 15, in __set__
raise TypeError, 'must be of type %r' % self.required_type
TypeError: must be of type <type 'int'>


If we're talking about optimizations, there's no reason something like
Psyco couldn't specifically look for specific descriptors like
stricttype, and probably replace that implementation with something
better optimized.

Ian
 
B

Bengt Richter

Quoth Rim:
[...]
The idea is to separate the value assignment from the type assignment,
by introducing a new operator, let's say the ':=' operator.

'=' remains what it is, assigns type and value at the same time
':=' assigns only the value

That's a peculiar way of speaking. '=' does not assign type and
value to a variable; it assigns an object to a name.

[...]
In reality, := would just coerce the return type of the RHS of := to
the type of the variable on the LHS of the :=, preserving the value(s)
as best as it can.

"The type of the variable" is not a meaningful phrase in Python:
names do not have types; objects do. I assume you meant "the type
of the object the name on the lhs is presently bound to".

Coerce how? Do you want a new __magicmethod__ for this operation?

Would ':=' mutate the object presently bound to the lhs name
create a new object and rebind the lhs name? To be concrete:
One thought would be that it wouldn't change the current binding. E.g.,

x := y

could be a spelling of x.__update__(y) and would be an *expression* with
potential side effects. By convention, a reference to the updated same object
would be returned. I.e., x.__class__ would have to have

def __update__(self, other):
<optional use of other>
return self

For starters, you could just let AttributeError get raised if there's no __update__ method.
a = whatever
b = a
a := 7
assert a is b

Should that assertion succeed or fail, in general? (Note that it
fails in general if the ':=' is replaced with '=', even if
whatever == 7.)

For the above, it should succeed, since the binding will have been unchanged
Would ':=' support chaining? That is, would
a := b := <some expression>
work? If so, would it be equivalent to
Yes, it should work, but no, it wouldn't be equivalent to
_tmp = <some expression>
a := _tmp
b := _tmp
working left to right, it would presumably be equivalent to

a.__update__(b).__update__( said:
(except for the namespace pollution), to match the semantics of
chained '='? (Note that the assignments happen left to right.)

Would use of a name on the lhs of a ':=' cause that name to be
considered local, as such use with '=' does?
No, because it would be a named object reference for the purpose of looking
up the __update__ attribute in the non-sugar spelling of a:=b (which
is a.__update__(b)) -- i.e., it's an expression, not really an assignment statement.
Yikes. Why not raise NameError if the name on the lhs of ':=' is
not yet bound? Yes.


Your example
a = Currency(6)
a := 7
is highly strange. (Even if we ignore the question of *which*
currency is being represented; the example could easily be
restated with, say, FixedPoint or Rational or some such.)
I don't know that I would totally dismiss the usage. It could be
a concise notation for a possibly polymorphic update operation.

w = Widget(blah, blah)
w := ('blue','white') # changes fg/bg colors
w := (20,40) # changes position
w := Font('Times Roman', size=24)
w := 'some text appended to a text widget'

Of course, you could use a property very similarly, e.g.,
w.p = 'text ...'

I guess it's a matter of what kind of sugar you like ;-)
How is it that your users are happy to write Currency(...) when
first assigning to a name, but not happy doing so when assigning
later?

Do they not understand that '=' binds a name to an object? Are
they C programmers who think of assignment as copying a value from
one location in memory to another? Do they think of the first
assignment as a type declaration for the name? (If any of these
three are true, they need to be better educated -- they're all
fundamental misconceptions about Python.)
True, but if they did understand that a=b binds but a:=b updates??
Do they need a language with syntactic support for Currency
objects?
I'm not sure what that would mean.

OTOH, a:=b might lead to analogies with a+=b and other a<op>=b
and then where would we be ;-)

Let's see...

a+:=b => a.__update__(a.__add__(b))

?? ;-)

Regards,
Bengt Richter
 
S

Steven Taschuk

Quoth Bengt Richter:
[...suggests a := b equivalent to a.__update__(b)...]
I guess it's a matter of what kind of sugar you like ;-)

Quite.

[...syntactic support for Currency objects...]
I'm not sure what that would mean.

I meant a language like Python but with a Currency literal syntax.
With syntax such as, say,
123.45$ equivalent to Currency(123.45)
(except, perhaps, for decimal vs binary exactness issues), the
users could type
a = 7$
# ...
a = 6$
which is less onerous than writing Currency(7), Currency(6).

This idea is well-suited to the scenario in which the users make
pervasive use of objects of this type, and the main problem is
having to type "Currency" over and over again.

[...]
 
B

Bengt Richter

Quoth Bengt Richter:
[...suggests a := b equivalent to a.__update__(b)...]
I guess it's a matter of what kind of sugar you like ;-)

Quite.

[...syntactic support for Currency objects...]
I'm not sure what that would mean.

I meant a language like Python but with a Currency literal syntax.
With syntax such as, say,
123.45$ equivalent to Currency(123.45)
(except, perhaps, for decimal vs binary exactness issues), the
users could type
a = 7$
# ...
a = 6$
which is less onerous than writing Currency(7), Currency(6).

This idea is well-suited to the scenario in which the users make
pervasive use of objects of this type, and the main problem is
having to type "Currency" over and over again.

Hm, it just occurred to me that one could have yet another form of sugar
[ It's now later, and I think this may be more than sugar ;-) ]
to help with this, by treating the dot operator slightly more symmetrically,
like, e.g., '+'.

Example, then explanation:

a = 7 .USD # (using symbols from http://www.xe.com/iso4217.htm by convention, no hard defs)
# ...
a = 6 .USD

This example uses the fact that int has no methods with plain names, so 6 .__getattribute__('USD')
will fail. Normally that would be the AttributeError end of it, but if '.' were like '+', we could
look for the r-version of __getattribute__ on the other object, analogous to __radd__. Thus

6 .USD # => USD.__rgetattribute__(6)

(note necessary disambiguating space here is not necessary in unambiguous contexts)
could return a specific currency object, e.g., if USD were defined something like e.g.,
(untested sketch !!)
--
class XXX_Currency(FixedPoint):
def __init__(self, symbol, *valprec):
FixedPoint.__init__(self, *valprec)
self.symbol = symbol
def __rgetattribute__(self, other):
return self.__class__(self.symbol, other, self.get_precision())

USD = XXX_Currency('USD')
--
Alternatively, perhaps USD could be a class from a factory instead of an instance, and have
__rgetattribute__ as a class method.

BTW,

num = 6
num = num.USD

would do the expected. I.e., this would be a dynamic mechanism, not a tweaked literal syntax.

Obviously this could be used for other quantity units than currencies, e.g.,

distance_to_moon = 384467 .km # avg dist

I.e., the mechanism is conceived in terms of an __rgetattribute__ method analogous to __radd__,
where you look for a compatible method in the other if the left arg can't handle it.
This could then allow a general mechanism for all types, not just numerics, yet constrain it
to special methods, to avoid accidental connections. Thus for an int,

6 . USD # => USD.__rgetattr__(6)

but also, this kind of binary-op attribute computation could supply missing methods, e.g.,
for simple file objects. Thus

f.readlines()

would effectively become

readlines.__rgetattribute__(f)()

if there were no readlines method on f, and this could
wrap f on the fly to provide the missing readlines method.

Am I being a troublemaker? ;-)

Regards,
Bengt Richter
 
I

Irmen de Jong

Bengt said:
Hm, it just occurred to me that one could have yet another form of sugar
[ It's now later, and I think this may be more than sugar ;-) ]
to help with this, by treating the dot operator slightly more symmetrically,
like, e.g., '+'.

[...lots of interesting examples...]
f.readlines()

would effectively become

readlines.__rgetattribute__(f)()

if there were no readlines method on f, and this could
wrap f on the fly to provide the missing readlines method.

My head spins... I don't know if I should consider these ideas very
powerful -- or very evil... My guts tell me that this could be very
powerful indeed. However my intuition tells me that there is a big
catch about this, but I don't know what it is yet... :p

--Irmen
 
I

Ian Bicking

Hm, it just occurred to me that one could have yet another form of sugar
[ It's now later, and I think this may be more than sugar ;-) ]
to help with this, by treating the dot operator slightly more symmetrically,
like, e.g., '+'.

Example, then explanation:

a = 7 .USD # (using symbols from http://www.xe.com/iso4217.htm by convention, no hard defs)
# ...
a = 6 .USD

Does 7 .USD *really* look that much better than USD(7)? And, really,
how often do you need currency literals? All my currency values come
from non-literal sources -- databases and XML files, an excel
spreadsheet, etc. I don't think this objection is particular to
currency either -- for most types this is true. If you are using them a
lot, you are acquiring them from non-code sources.

Though I suppose Rebol would be a counterexample. And, in the case of
scripting (a kind of programming, not a kind of programming language :)
it might be a more valid need. But most of Rebol's interesting data
types aren't syntactically valid in Python anyway.

Ian
 
R

Rim

Everyone! Thanks a lot for the insightful and enlightening discussion.

I think the suggestion to solve the problem by using the __setattr__
special method will not work because it intercepts attribute
assignment of the form "self.attr = somthing", and not what I want,
which is "name = something".

John suggested to look at properties, this is the closest I can get to
the
behavior I am looking for, but from my understanding, that will let me
intercept "name.value = something" instead of intercepting "name =
something".

Thank you very much everyone.
- Rim
 
M

Michele Simionato

Everyone! Thanks a lot for the insightful and enlightening discussion.

I think the suggestion to solve the problem by using the __setattr__
special method will not work because it intercepts attribute
assignment of the form "self.attr = somthing", and not what I want,
which is "name = something".

John suggested to look at properties, this is the closest I can get to
the
behavior I am looking for, but from my understanding, that will let me
intercept "name.value = something" instead of intercepting "name =
something".

Thank you very much everyone.
- Rim

Okay, you asked for it ;)

class MetaTrick(type):
def __init__(cls,name,bases,dic):
super(MetaTrick,cls).__init__(name,bases,dic)
for (k,v) in dic.iteritems():
if not k.endswith("__"): setattr(cls,k,v)
def __setattr__(cls,k,v):
print "Intercepting %s = %s" % (k,v)
super(MetaTrick,cls).__setattr__(k,v)

class C:
__metaclass__=MetaTrick
name='something'

This gives the output "Intercepting name = something".

I show you this snippet hoping you will not use it!

nothing-is-impossible-with-metaclasses-ly,

Michele
 
A

Aahz

I think the suggestion to solve the problem by using the __setattr__
special method will not work because it intercepts attribute
assignment of the form "self.attr = somthing", and not what I want,
which is "name = something".

Why don't you follow my suggestion about creating your own language?
 
R

Rim

I think the suggestion to solve the problem by using the __setattr__
Why don't you follow my suggestion about creating your own language?

I think it would be too much work for one person, and I don't know
how, and I don't think I have the time. Thanks but for now this is
beyond my limited capabilities!

Rim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top