Syntax for easy binding __name__, __module__, __qualname__ to arbitrary objects
In discussion about http://www.python.org/dev/peps/pep-0435/ being mentioned problems with enums created with functional syntax: - need for explicit pass of type name to "function" that create enum type: Animals = Enum('Animals', 'dog cat bird') which violates DRY - pickling which requires __module__ atribute to be set, proposed solutions for this was either use non-portable and fragile _getframe hack, or explicit pass module name: Animals = Enum('animals.Animals', 'dog cat bird') aside violating DRY, this solution also has other problems, it won't work correctly if module is executed directly, and will break if pickling nested classes from http://www.python.org/dev/peps/pep-3154/ will be implemented (it may by bypassed by either provide separate arguments for module and type name, or using different separator for separating module and class name) These also apply for other objects like NamedTuple or mentioned NamedValues. To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp with new syntax declaring Enum will look like def Animals = Enum('dog cat bird') as pointed by Larry it may be done using existing syntax in form: @Enum('dog cat bird') def Animals(): pass but it's ugly, and may by confusing. Other examples: def MyTuple = NamedTuple("a b c d") def PI = NamedValue(3.1415926) -- 闇に隠れた黒い力 弱い心を操る
On 9 May 2013 11:29, Piotr Duda <duda.piotr@gmail.com> wrote:
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
Just for clarification, if you used this syntax with an expression which returned an object which *didn't* allow attributes to be set, I assume it would simply fail at runtime with an AttributeError? For example, def x = 12 This isn't a point against the syntax, I just think it's worth being explicit that this is what would happen. Overall, I'm somewhat indifferent. The use case seems fairly specialised to me, and yet the syntax "def name = value" seems like it's worth reserving for something a bit more generally useful. Maybe the def name=value syntax should implement a protocol, that objects like enum and namedtuple subclasses can hook into (in the same way that the context manager and iterator protocols work, or indeed the whole class definition mechanism). Paul
2013/5/9 Paul Moore <p.f.moore@gmail.com>:
On 9 May 2013 11:29, Piotr Duda <duda.piotr@gmail.com> wrote:
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
Just for clarification, if you used this syntax with an expression which returned an object which *didn't* allow attributes to be set, I assume it would simply fail at runtime with an AttributeError? For example,
def x = 12
Yes, it fails, I thought about ignoring exceptions on attribute assignment, but then the syntax wouldn't provide any guarantees and in those cases it will be equivalent of simple assignment.
This isn't a point against the syntax, I just think it's worth being explicit that this is what would happen.
Overall, I'm somewhat indifferent. The use case seems fairly specialised to me, and yet the syntax "def name = value" seems like it's worth reserving for something a bit more generally useful.
Maybe the def name=value syntax should implement a protocol, that objects like enum and namedtuple subclasses can hook into (in the same way that the context manager and iterator protocols work, or indeed the whole class definition mechanism).
This may be good idea. -- 闇に隠れた黒い力 弱い心を操る
On 9 May 2013 21:53, "Piotr Duda" <duda.piotr@gmail.com> wrote:
2013/5/9 Paul Moore <p.f.moore@gmail.com>:
On 9 May 2013 11:29, Piotr Duda <duda.piotr@gmail.com> wrote:
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
Just for clarification, if you used this syntax with an expression which returned an object which *didn't* allow attributes to be set, I assume it would simply fail at runtime with an AttributeError? For example,
def x = 12
Yes, it fails, I thought about ignoring exceptions on attribute assignment, but then the syntax wouldn't provide any guarantees and in those cases it will be equivalent of simple assignment.
This isn't a point against the syntax, I just think it's worth being explicit that this is what would happen.
Overall, I'm somewhat indifferent. The use case seems fairly
specialised to
me, and yet the syntax "def name = value" seems like it's worth reserving for something a bit more generally useful.
Maybe the def name=value syntax should implement a protocol, that objects like enum and namedtuple subclasses can hook into (in the same way that
One more possible colour for the bikeshed: name def= expression the
context manager and iterator protocols work, or indeed the whole class definition mechanism).
This may be good idea.
An intriguing idea, indeed. I can't promise I'll approve of the end result, but I think a PEP proposing a name binding protocol that passes in the module name, the "location" within the module (when inside a function or class) and the target name could be worth reading. Directly setting __module__, __name__ and __qualname__ may be a reasonable default behaviour. The new syntax is essentially competing with the current implicit-but-fragile stack introspection and the explicit-but-cumbersome passing of the target name. Even if the ultimate verdict ends being "not worth the hassle", we would at least have a common reference point when this discussion next comes up (it seems to be every couple of years or so). Cheers, Nick.
-- 闇に隠れた黒い力 弱い心を操る _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On 09/05/2013 15:08, Nick Coghlan wrote:
On 9 May 2013 21:53, "Piotr Duda" <duda.piotr@gmail.com <mailto:duda.piotr@gmail.com>> wrote:
2013/5/9 Paul Moore <p.f.moore@gmail.com <mailto:p.f.moore@gmail.com>>:
On 9 May 2013 11:29, Piotr Duda <duda.piotr@gmail.com
<mailto:duda.piotr@gmail.com>> wrote:
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
One more possible colour for the bikeshed:
name def= expression
Considering that attributes of 'name' are also being set, how about: name .= expression
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
Just for clarification, if you used this syntax with an expression which returned an object which *didn't* allow attributes to be set, I assume it would simply fail at runtime with an AttributeError? For example,
def x = 12
Yes, it fails, I thought about ignoring exceptions on attribute assignment, but then the syntax wouldn't provide any guarantees and in those cases it will be equivalent of simple assignment.
This isn't a point against the syntax, I just think it's worth being explicit that this is what would happen.
Overall, I'm somewhat indifferent. The use case seems fairly specialised to me, and yet the syntax "def name = value" seems like it's worth reserving for something a bit more generally useful.
Maybe the def name=value syntax should implement a protocol, that objects like enum and namedtuple subclasses can hook into (in the same way that the context manager and iterator protocols work, or indeed the whole class definition mechanism).
This may be good idea.
An intriguing idea, indeed. I can't promise I'll approve of the end result, but I think a PEP proposing a name binding protocol that passes in the module name, the "location" within the module (when inside a function or class) and the target name could be worth reading.
Directly setting __module__, __name__ and __qualname__ may be a reasonable default behaviour.
The new syntax is essentially competing with the current implicit-but-fragile stack introspection and the explicit-but-cumbersome passing of the target name.
Even if the ultimate verdict ends being "not worth the hassle", we would at least have a common reference point when this discussion next comes up (it seems to be every couple of years or so).
Paul Moore wrote:
Overall, I'm somewhat indifferent. The use case seems fairly specialised to me, and yet the syntax "def name = value" seems like it's worth reserving for something a bit more generally useful.
Not sure about the syntax, but I for one would find something like this useful for other purposes. For example, in some of my libraries I have a function that creates a special kind of property that needs to know its own name. Currently you have to write it like this: class Foo: blarg = overridable_property('blarg', "The blarginess of the Foo") which is an annoying DRY violation. Using the proposed syntax, it could be written class Foo: def blarg = overridable_property("The blarginess of the Foo")
Maybe the def name=value syntax should implement a protocol,
Hmmm. Maybe def name = value could turn into name = value.__def__('name', __name__) -- Greg
On 10 May 2013 00:08, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Hmmm. Maybe
def name = value
could turn into
name = value.__def__('name', __name__)
Yes, that's the sort of thing I had in mind, although it hadn't occurred to me that it would be as simple as this. To satisfy the original use case, it would need the module the name is defined in passed to __def__ as well. One other possibility would be passing the containing class, too, but I don't think that's needed - the metaclass machinery gives us the means to play class-based tricks already. Paul
On Fri, May 10, 2013 at 2:08 AM, Greg Ewing <greg.ewing@canterbury.ac.nz>wrote:
Paul Moore wrote:
Overall, I'm somewhat indifferent. The use case seems fairly specialised to me, and yet the syntax "def name = value" seems like it's worth reserving for something a bit more generally useful.
Hmmm. Maybe
def name = value
could turn into
name = value.__def__('name', __name__)
C-esque macros (and perhaps macropy) could implement this and decorators under the same roof. Perhaps we can somehow solve the more general problem without introducing macro kludge? Yuval
On 9 May 2013, at 11:29, Piotr Duda <duda.piotr@gmail.com> wrote:
These also apply for other objects like NamedTuple or mentioned NamedValues.
+1 I really like this proposal. In one of my libraries, I actually went one step further with the frame hack and injected the resulting object into the parent namespace in an attempt to avoid the duplicated declaration of the name. The syntax was something like: namedtuple.MyName("foo", "bar") as a bare statement, which would inject "MyName" into the namespace (using the frame hack) as well as getting the right module name. This is obviously very, very ugly. :-)
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression
FWIW, this syntax looks the most obvious to me, as it clearly communicates both the assignment of the return value of the expression to the name, and the fact that name is a local definition (and thus likely to acquire additional properties). Cheers, Martin
other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
with new syntax declaring Enum will look like def Animals = Enum('dog cat bird')
as pointed by Larry it may be done using existing syntax in form: @Enum('dog cat bird') def Animals(): pass
but it's ugly, and may by confusing.
Other examples: def MyTuple = NamedTuple("a b c d") def PI = NamedValue(3.1415926)
-- 闇に隠れた黒い力 弱い心を操る _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
Since MacroPy came up, I thought I should chime in (since I made it) Macros could pretty trivially give you any syntax that doesn't blow up in a SyntaxError. that means "def x = y" is out of the question, unless someone changes the python runtime's lexer/parser to make that work. What we have right now: @case class MyName(foo, bar): pass allows the class to have methods (put them in place of the `pass`, just like any other class) and generally looks quite pretty. It would be ~50 lines of code to make a macro for: @enum class MyEnum(Red, White, Blue): pass or: @enum class MyEnum(index): Monday(0) Tuesday(1) ' Wednesday(2) ... I would argue that "solving the more general problem" is exactly what macros are about (if you think Macros are sketchy, you should look at the implementation of namedtuple!), but I'm sure many would disagree. I just wanted to clarify what is possible. -Haoyi On Sun, May 12, 2013 at 8:44 PM, Martin Morrison <mm@ensoft.co.uk> wrote:
On 9 May 2013, at 11:29, Piotr Duda <duda.piotr@gmail.com> wrote:
These also apply for other objects like NamedTuple or mentioned NamedValues.
+1 I really like this proposal.
In one of my libraries, I actually went one step further with the frame hack and injected the resulting object into the parent namespace in an attempt to avoid the duplicated declaration of the name. The syntax was something like:
namedtuple.MyName("foo", "bar")
as a bare statement, which would inject "MyName" into the namespace (using the frame hack) as well as getting the right module name. This is obviously very, very ugly. :-)
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression
FWIW, this syntax looks the most obvious to me, as it clearly communicates both the assignment of the return value of the expression to the name, and the fact that name is a local definition (and thus likely to acquire additional properties).
Cheers, Martin
other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
with new syntax declaring Enum will look like def Animals = Enum('dog cat bird')
as pointed by Larry it may be done using existing syntax in form: @Enum('dog cat bird') def Animals(): pass
but it's ugly, and may by confusing.
Other examples: def MyTuple = NamedTuple("a b c d") def PI = NamedValue(3.1415926)
-- 闇に隠れた黒い力 弱い心を操る _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
On May 9, 2013, at 3:29 AM, Piotr Duda <duda.piotr@gmail.com> wrote:
Animals = Enum('Animals', 'dog cat bird') which violates DRY
This is a profound misreading of DRY which is all about not repeating big chunks of algorithmic logic. The above code is clear and doesn't require any special syntax. Remember, that is why we got rid of the print-keyword in favor of the print-function (the new print requires no special rules and works like ordinary functions). Also remember that Enum and NamedTuple calls typically only occur once in code. The bulk of the code simply *uses* the declared enumerations or named tuples. In other words, you're inventing new syntax to solve a very unimportant problem. In most code, you will save perhaps one single word, but it will come at the expense of an even and odd and unexpected 'def' syntactic magic: old: Animals = Enum('Animals', 'dog cat bird') new: def Animals = Enum('dog cat bird') net savings: 6 characters net loss: complexify Enum, abuse the def syntax, looks weird, ... old: a = Animals.dog; b=Animals.cat new: a = Animals.dog; b=Animals.cat new change where it matters: zero!
These also apply for other objects like NamedTuple or mentioned NamedValues.
To solve these problems I propose to add simple syntax that assigns these attributes to arbitrary object: def name = expression other possible forms may be: def name from expression class name = expression class name from expression name := expression # new operator
which would be equivalent for: _tmp = expression _tmp.__name__ = 'name' _tmp.__qualname__ = ... # corresponding qualname _tmp.__module__ = __name__ # apply decorators if present name = _tmp
<snip>
Other examples: def MyTuple = NamedTuple("a b c d") def PI = NamedValue(3.1415926)
Sorry, but I think this is yet another terrible idea. People like Python because of its beautiful and intuitive syntax. Why throw that out the window for such an unimportant problem? Raymond
On May 12, 2013, at 6:53 PM, Ned Batchelder <ned@nedbatchelder.com> wrote:
Sorry, but I think this is yet another terrible idea.
This seems uncivil to me.
Really, we can't say we think something is a really bad idea? New wording: "Sorry, but I think this proposal may not be a net positive for the language." Raymond
On 5/12/2013 10:09 PM, Raymond Hettinger wrote:
On May 12, 2013, at 6:53 PM, Ned Batchelder <ned@nedbatchelder.com <mailto:ned@nedbatchelder.com>> wrote:
Sorry, but I think this is yet another terrible idea.
This seems uncivil to me.
Really, we can't say we think something is a really bad idea?
New wording: "Sorry, but I think this proposal may not be a net positive for the language."
Raymond, I apologize. I probably misread your intent. I certainly didn't mean to make you feel unwelcome. I wanted to be sure the people proposing ideas don't feel unwelcome. --Ned.
Raymond
On Sun, May 12, 2013 at 9:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On May 9, 2013, at 3:29 AM, Piotr Duda <duda.piotr@gmail.com> wrote:
Animals = Enum('Animals', 'dog cat bird') which violates DRY
This is a profound misreading of DRY which is all about not repeating big chunks of algorithmic logic.
DRY, like most heuristics, is about making mistakes less likely. Mistakes are likely with huge chunks of repeated logic, because people are inclined to fix things at only one location. Mistakes are likely with the above because it is conceptually only one location, but syntactically two -- and doing something different in the second location is a mistake that the compiler won't catch. The problem with
Animals = Enum('Animals', 'dog cat bird')
is that you might accidentally type
Animals = Enum('Animal', 'dog cat bird') or Anmals = Enum('Animals', 'dog cat bird')
instead. -jJ
On Mon, May 13, 2013 at 8:49 AM, Jim Jewett <jimjjewett@gmail.com> wrote:
The problem with
Animals = Enum('Animals', 'dog cat bird')
is that you might accidentally type
Animals = Enum('Animal', 'dog cat bird') or Anmals = Enum('Animals', 'dog cat bird')
instead.
Sure. But coming up with a syntactic solution for this issue is not easy. So far all the proposals from this thread (and from past threads trying to address the same issues, including PEP 403) look terrible to me -- none of the proposals are more than random permutations of symbols that are currently syntactically invalid are given a fairly random new meaning. So in the mean time please live with the slight redundancy in this case. Next time you may want to try and design syntax so that you won't have to type the same method name twice when you're defining a function and later calling it. :-) -- --Guido van Rossum (python.org/~guido)
On 13 May 2013, at 18:01, Guido van Rossum <guido@python.org> wrote:
On Mon, May 13, 2013 at 8:49 AM, Jim Jewett <jimjjewett@gmail.com> wrote:
The problem with
Animals = Enum('Animals', 'dog cat bird')
is that you might accidentally type
Animals = Enum('Animal', 'dog cat bird') or Anmals = Enum('Animals', 'dog cat bird')
instead.
Sure. But coming up with a syntactic solution for this issue is not easy. So far all the proposals from this thread (and from past threads trying to address the same issues, including PEP 403) look terrible to me -- none of the proposals are more than random permutations of symbols that are currently syntactically invalid are given a fairly random new meaning.
Guido, it sounds like you are not completely opposed to the general idea here, but rather find all the proposed syntaxes to be ugly? This also ties in to your comments about the getframe hack in the new Enum implementation - I think everyone fully agrees with your comments about making things easier for everyone, I'm just not so sure that stack introspection is the best solution, irrespective of the difficulty of implementation in other implementations. It just feels too "magic" and implicit - and we all know explicit is better. Bruce Leban just today on another python-ideas thread said something far more clearly than I ever could to explain why the "def Foo = <expr>" syntax feels "right" to me; to quote, including his example: Def is not a constructor. It is an assignment statement. def f(x): return x+1 f = lambda x: x+1 are equivalent. With this interpretation of def, it feels perfect. :-) Anyway, just my 2 cents. Cheers, Martin
So in the mean time please live with the slight redundancy in this case. Next time you may want to try and design syntax so that you won't have to type the same method name twice when you're defining a function and later calling it. :-)
-- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
Martin Morrison wrote:
Def is not a constructor. It is an assignment statement.
It's really some of both. It constructs a function object, and binds it to a name. These two operations are entwined, in a sense, because the name being bound to is used as an argument in the construction. We're after a generalisation of this entwined construction-and-assignment. (Astruction? Consignment?) I'm not entirely happy with the current proposal: def name = expr because it doesn't fully entwine. The expr can be a constructor, but doesn't have to be, and even when it is, the construction occurs separately from the assignment. Also, it looks like an ordinary assignment with 'def' stuck in front, which, as Guido points out, seems somewhat random. I'd like to propose something a bit different: def name as expr(arg, ...) which would expand to something like name = expr(arg, ..., __name__ = 'name', __module__ = 'module') For example, def Animal as Enum('cat dog platypus') This reads quite naturally: "define Animal as an Enum with these arguments." Another example based on my own use case: def width as overridable_property("The width of the widget.") (Yes, this is yet another, different use of the word "as", but I don't see anything wrong with that. Small words in English often don't mean much on their own and derive most of their meaning from their context.) -- Greg
On 05/13/2013 05:03 PM, Greg Ewing wrote:
I'd like to propose something a bit different:
def name as expr(arg, ...)
which would expand to something like
name = expr(arg, ..., __name__ = 'name', __module__ = 'module')
For example,
def Animal as Enum('cat dog platypus')
This reads quite naturally: "define Animal as an Enum with these arguments."
Another example based on my own use case:
def width as overridable_property("The width of the widget.")
+1 for the idea
(Yes, this is yet another, different use of the word "as", but I don't see anything wrong with that. Small words in English often don't mean much on their own and derive most of their meaning from their context.)
and another +1 :) -- ~Ethan~
On Mon, May 13, 2013 at 8:03 PM, Greg Ewing <greg.ewing@canterbury.ac.nz>wrote:
I'd like to propose something a bit different:
def name as expr(arg, ...)
Note that in the current uses of "as" the assignment target is on the right: with expr as name: import pkg.mod as name I am not sure "def expr(...) as name" is any better than Greg's original proposal, though.
On 14/05/13 01:49, Jim Jewett wrote:
On Sun, May 12, 2013 at 9:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On May 9, 2013, at 3:29 AM, Piotr Duda <duda.piotr@gmail.com> wrote:
Animals = Enum('Animals', 'dog cat bird') which violates DRY
This is a profound misreading of DRY which is all about not repeating big chunks of algorithmic logic.
DRY, like most heuristics, is about making mistakes less likely.
Mistakes are likely with huge chunks of repeated logic, because people are inclined to fix things at only one location.
Mistakes are likely with the above because it is conceptually only one location, but syntactically two -- and doing something different in the second location is a mistake that the compiler won't catch.
"Likely"? I think not. If you (generic "you", not you personally) are such a careless coder that you are *likely* to mistype the name in a *single line* like `name = Enum("name", "items...")` then there is probably no help for you. Mistakes happen to the best of us, but they are *rare*. Besides, strictly speaking setting the Enum name different to the name being bound is not necessarily an error. We can, and frequently do, define functions with one name and then bind them to a different name, e.g. in decorators. Having to repeat the name is a wart, but it is a tiny wart, and surely not worth the amount of angst it apparently causes.
The problem with
Animals = Enum('Animals', 'dog cat bird')
is that you might accidentally type
Animals = Enum('Animal', 'dog cat bird') or Anmals = Enum('Animals', 'dog cat bird')
instead.
In which case, either something will break, and you will fix the broken code, and then it will work, or nothing will break, in which case it almost certainly doesn't matter and you can pretend you did it on purpose. -- Steven
On Tue, May 14, 2013 at 10:03 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Martin Morrison wrote:
Def is not a constructor. It is an assignment statement.
It's really some of both. It constructs a function object, and binds it to a name. These two operations are entwined, in a sense, because the name being bound to is used as an argument in the construction.
We're after a generalisation of this entwined construction-and-assignment. (Astruction? Consignment?) I'm not entirely happy with the current proposal:
def name = expr
because it doesn't fully entwine. The expr can be a constructor, but doesn't have to be, and even when it is, the construction occurs separately from the assignment. Also, it looks like an ordinary assignment with 'def' stuck in front, which, as Guido points out, seems somewhat random.
It doesn't seem random to me, and it ties into some conceptual issues I've been pondering for a long time. I see this as potentially related to PEP 395 and the way __main__ and pseudo-modules are prone to breaking pickle and exposing implementation details, as well as to the fact we added @functools.wraps to preserve the original location details of decorated functions. It also ties into Guido's rationale for wanting to preserve the way that namedtuple (and now enum) special case simple module level assignments to help ensure that pickling works as expected (see http://bugs.python.org/issue17947#msg189160) What Piotr's proposal crystalised for me is the idea that we really have two different kinds of name binding in Python. I'm going to call them "incidental binding" and "definitive binding". Incidental binding is the way we normally think about variables in Python - it's just a local name that is used to reference an object. Objects know nothing about their incidental bindings, and (aside from function parameters and keyword arguments) the specific name used is typically of no interest outside the scope where the binding happens. For loops, with statements, except clauses, assignment statements - these all create incidental bindings, where the target name and the location of the binding is of no interest to the object being bound. By contrast, definitive bindings are exactly those where the object being bound *cares* about the name and where it happens. The most obvious case where the definitive binding matters is pickle compatibility, because the definitive name is what gets used to retrieve the appropriate code when unpickling. However, it also affects introspection, as the existence of @functools.wraps shows. def statements and class statements are definitive bindings - through the constructor, they set __name__, __qualname__ and __module__ on the bound object based on the location of the statement itself. @functools.wraps works because it converts a normally definitive binding (the declaration of a wrapper function) into an incidental binding by copying the definitive binding details from the function being wrapped. What the frame introspection hack in namedtuple and enum achieves is to allow an ordinary module level assignment to be treated as definitive - these APIs require that the name be passed in explicitly, but if the module is omitted they can look it up in the globals of the calling frame (Guido proposes that we provider a nicer API for getting that dynamic context information, and I'm now persuaded that he's right, but that's independent of the underlying conceptual issue). The reason I'm a big fan of Piotr's idea in general, and the "def NAME = EXPR" syntax in particular is that I think it takes this currently implicit, nebulous concept and elevates it to the status it deserves: giving an object an incidental label for convenient local reference and defining the *canonical* name for that object are different operations, and it is reasonable to provide an explicit syntax for the latter that is distinct-from-but-related-to the syntax for the former. Specifically, as Piotr suggested, I would like to see "def <NAME> = <EXPR>" become syntactic sugar for something like: # We start with an ordinary incidental binding <NAME> = _ref = <EXPR> if hasattr(_ref, "__defname__"): # We elevate this to a definitive binding by passing # __name__, __qualname__ and __module__ to the bound object _ref.__defname__("<NAME>", <QUALIFER> + "<NAME>", __name__) The beauty of this syntax is that it means if we define __defname__ appropriately on function objects and on type, then we can cleanly separate the "object creation" step from the "name binding" step, allowing functional APIs to exist on an equal playing field with the dedicated syntax. Such a change would also help explain why we *don't* allow arbitrary assignment targets in a def statement - as a definitive name binding, there are additional constraints that don't apply to an incidental binding.
This reads quite naturally: "define Animal as an Enum with these arguments."
Whereas I prefer the idea that the RHS is created as an anonymous object, and then bound to a canonical name.
Another example based on my own use case:
def width as overridable_property("The width of the widget.")
(Yes, this is yet another, different use of the word "as", but I don't see anything wrong with that. Small words in English often don't mean much on their own and derive most of their meaning from their context.)
Strong -1. We've had this discussion before, and any use of "as" should be to bind to a name on the RHS and the value bound should *not* be the expression on the LHS, but some related object (an imported module, a caught exception, the result of an __enter__ method). If the expression is being bound directly, then the name should appear on the LHS and use "=" as the symbol. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Mon, May 13, 2013 at 8:49 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
What Piotr's proposal crystalised for me is the idea that we really have two different kinds of name binding in Python. I'm going to call them "incidental binding" and "definitive binding". ...
Nice and clear explanation.
if hasattr(_ref, "__defname__"): ...
The beauty of this syntax is that it means if we define __defname__ appropriately on function objects and on type, then ...
Why make __defname__ optional? If the author explicitly sticks a def in front of an assignment when they shouldn't, I think that should be an error. Do you really want: def a = 3 to be allowed? --- Bruce Latest blog post: Alice's Puzzle Page http://www.vroospeak.com Learn how hackers think: http://j.mp/gruyere-security
On Tue, May 14, 2013 at 2:40 PM, Bruce Leban <bruce@leapyear.org> wrote:
On Mon, May 13, 2013 at 8:49 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
What Piotr's proposal crystalised for me is the idea that we really have two different kinds of name binding in Python. I'm going to call them "incidental binding" and "definitive binding". ...
Nice and clear explanation.
if hasattr(_ref, "__defname__"): ...
The beauty of this syntax is that it means if we define __defname__ appropriately on function objects and on type, then ...
Why make __defname__ optional? If the author explicitly sticks a def in front of an assignment when they shouldn't, I think that should be an error. Do you really want:
def a = 3
to be allowed?
Good point. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
Nick Coghlan wrote:
We've had this discussion before, and any use of "as" should be to bind to a name on the RHS
I don't remember any consensus being reached on this. My opinion is that imposing any such restriction on the use of "as" would be a foolish consistency that rules out a lot of natural-sounding constructs. -- Greg
On Tue, May 14, 2013 at 4:37 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Nick Coghlan wrote:
We've had this discussion before, and any use of "as" should be to bind to a name on the RHS
I don't remember any consensus being reached on this. My opinion is that imposing any such restriction on the use of "as" would be a foolish consistency that rules out a lot of natural-sounding constructs.
As far as I recall, it wasn't consensus, it was Guido saying "No!" to something I suggested and then me agreeing that his objections made sense :) (I forget what I was proposing at the time, though, I just remember it involved a "NAME as EXPR" clause and Guido really didn't like it for the reasons I gave) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
Greg Ewing writes:
My opinion is that imposing any such restriction on the use of "as" would be a foolish consistency that rules out a lot of natural-sounding constructs.
Natural language is poorly fitted to be a programming language precisely because everything is possible. Not all natural constructs need to be anointed as Python syntax. It's especially important that constructs' semantics are indicated by their syntax. I suspect that use of both "... NAME as EXPR" and "... EXPR as NAME" would come at a readability cost. We should also remember that there are lots of Python programmers to whom none of the syntax that is natural-sounding to the English- trained ear is particularly mnemonic. The consistent application of a few regular rules of formation and failure to adhere to idiomatic variants is one important reason you can typically distinguish native from non-native writing at a glance. I suspect that catering to this preference for consistency with existing simple rules will make it easier for anybody (regardless of mother tongue) to become fluent in Python. Regardless the decision about use of "NAME as EXPR" syntax, I'm +1 on Nick's explanation that the "def" keyword indicates definitive binding of a name occurs as well as incidental (formal) binding, while its absence means that incidental binding only occurs. For that reason I think the same "=" operator should be used to signify the incidental binding.
Stephen J. Turnbull writes:
Greg Ewing writes:
My opinion is that imposing any such restriction on the use of "as" would be a foolish consistency that rules out a lot of natural-sounding constructs.
Natural language is poorly fitted to be a programming language precisely because everything is possible. Not all natural constructs need to be anointed as Python syntax. It's especially important that constructs' semantics are indicated by their syntax. I suspect that use of both "... NAME as EXPR" and "... EXPR as NAME" would come at a readability cost.
We should also remember that there are lots of Python programmers to whom none of the syntax that is natural-sounding to the English- trained ear is particularly mnemonic. The consistent application of a few regular rules of formation and failure to adhere to idiomatic variants is one important reason you can typically distinguish native from non-native writing at a glance. I suspect that catering to this preference for consistency with existing simple rules will make it easier for anybody (regardless of mother tongue) to become fluent in Python. ...
Stepping in other peoples shoes and looking through their glasses in my experience does not always produce meaningful perceptions, esp. when core concepts of life - like "nativeness" of language - are involved. There may be no excuse for a programmer to not learn the world language english, but prefering simple consistently applied rules will presumably enhance every language in creative use. The message is rarely inside a word or a simple phrase, isn't it? All the best-native-ltr-but-non-native-English-greetings, Stefan
On May 14, 2013, at 01:49 PM, Nick Coghlan wrote:
What Piotr's proposal crystalised for me is the idea that we really have two different kinds of name binding in Python. I'm going to call them "incidental binding" and "definitive binding".
Perhaps "definitional" is another way to describe the latter. class statements and def statements both define a new object and bound that new object to a name. -Barry
On Mon, May 13, 2013 at 9:42 PM, Steven D'Aprano <steve@pearwood.info> wrote:
On 14/05/13 01:49, Jim Jewett wrote:
On Sun, May 12, 2013 at 9:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On May 9, 2013, at 3:29 AM, Piotr Duda <duda.piotr@gmail.com> wrote:
Animals = Enum('Animals', 'dog cat bird') which violates DRY
This is a profound misreading of DRY which is all about not repeating big chunks of algorithmic logic.
DRY, like most heuristics, is about making mistakes less likely.
Mistakes are likely with huge chunks of repeated logic, because people are inclined to fix things at only one location.
Mistakes are likely with the above because it is conceptually only one location, but syntactically two -- and doing something different in the second location is a mistake that the compiler won't catch.
"Likely"? I think not.
If you (generic "you", not you personally) are such a careless coder that you are *likely* to mistype the name in a *single line* like `name = Enum("name", "items...")` then there is probably no help for you. Mistakes happen to the best of us, but they are *rare*.
Likely relative to other my other mistakes, yes.
Besides, strictly speaking setting the Enum name different to the name being bound is not necessarily an error. We can, and frequently do, define functions with one name and then bind them to a different name, e.g. in decorators.
If the name didn't need to match, then you could just as well use strings and retype them everywhere. It would be caught (or not) in testing ... Wanting to ensure that typos don't slip in -- even to documentation-only sections -- is much of the motivation for enums, as well as for the recurrent calls for a make statement.
is that you might accidentally type ...
Anmals = Enum('Animals', 'dog cat bird')
In which case, either something will break, and you will fix the broken code, and then it will work, or nothing will break, in which case it almost certainly doesn't matter and you can pretend you did it on purpose.
I regularly maintain Java (and formerly C) code in which a name was misspelled, or even just spelled oddly.* The code works, so long as other code is sufficiently consistent, but it is a lot harder to maintain. (That said, *most* of the extra problems are from grep not finding the code, which wouldn't be a problem for this *particular* case -- at least not until some other tool started using the class name for pickling or documentation or something.) * "oddly" can even mean "correctly", if the project otherwise uses abbreviations. -jJ
On Tue, May 14, 2013 at 06:43:10PM -0400, Jim Jewett wrote:
On Mon, May 13, 2013 at 9:42 PM, Steven D'Aprano <steve@pearwood.info> wrote:
On 14/05/13 01:49, Jim Jewett wrote:
On Sun, May 12, 2013 at 9:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On May 9, 2013, at 3:29 AM, Piotr Duda <duda.piotr@gmail.com> wrote:
Animals = Enum('Animals', 'dog cat bird') which violates DRY
This is a profound misreading of DRY which is all about not repeating big chunks of algorithmic logic.
DRY, like most heuristics, is about making mistakes less likely.
Mistakes are likely with huge chunks of repeated logic, because people are inclined to fix things at only one location.
Mistakes are likely with the above because it is conceptually only one location, but syntactically two -- and doing something different in the second location is a mistake that the compiler won't catch.
"Likely"? I think not.
If you (generic "you", not you personally) are such a careless coder that you are *likely* to mistype the name in a *single line* like `name = Enum("name", "items...")` then there is probably no help for you. Mistakes happen to the best of us, but they are *rare*.
Likely relative to other my other mistakes, yes.
I see what you did there :-) But seriously, *in my experience*, mere typos of names are not usually critical errors. They are usually discovered and fixed immediately when the code raises a NameError. So I have little (not zero, but *little*) concern about the risk of typos like: Animals = Enum("Animal", "cow dog cat sheep") Chances are that you will detect this immediately you try to run the code, when Animal.cow raises NameError. Either way though, it's an easy error to fix, and an easy mistake for a linter to check. So why I acknowledge that *in principle* this is a weakness of Python that can lead to bugs, it is my opinion that *in practice* it is close enough to harmless that there's no reason to rush into a sub-optimal fix for it.
Besides, strictly speaking setting the Enum name different to the name being bound is not necessarily an error. We can, and frequently do, define functions with one name and then bind them to a different name, e.g. in decorators.
If the name didn't need to match, then you could just as well use strings and retype them everywhere. It would be caught (or not) in testing ...
The advantage of having Animal.cow, Animal.dog etc. is to standardise on the cow and dog parts, not the Animal part. Animal is mostly just the container, it is the enums that you usually care about. For many purposes, we won't even care about the container. We'll do something like this: Directions = Enum("Directions", "UP DOWN LEFT RIGHT") globals().update(Directions.members()) (have I got the name "members" right?) and then always refer to UP, DOWN etc. directly. If we don't care about pickling the enums, and do care about "namespace pollution", we might even `del Directions` and be done with it.
Wanting to ensure that typos don't slip in -- even to documentation-only sections -- is much of the motivation for enums, as well as for the recurrent calls for a make statement.
I agree. Having to type the class name twice is a wart. But it's not a big one. More of a pimple. -- Steven
On 05/14/2013 04:19 PM, Steven D'Aprano wrote:
For many purposes, we won't even care about the container. We'll do something like this:
Directions = Enum("Directions", "UP DOWN LEFT RIGHT") globals().update(Directions.members())
(have I got the name "members" right?)
Almost. It's __members__. -- ~Ethan~
On 05/13/2013 10:49 PM, Nick Coghlan wrote:
What Piotr's proposal crystalised for me is the idea that we really have two different kinds of name binding in Python. I'm going to call them "incidental binding" and "definitive binding".
As far as conceptual aspects of python go, I like to think of the ':' as a associate operator. (*) This works both in function definitions, class definitions, and dictionary literals. The part before the colon is associated to the part after the colon. Then def, class, and {}'s, determine the specific type of association being made. So they are associate modifiers. In the case of class and def, it's associating a code object created from the following block of code. And in dictionary literals, it associates a key/object pair. An '=' is a simple name/object association with restrictions on the name, the object must already exist, and the associated pair is put in the current name space. Associate modifiers for '=' are 'global' and 'non_local'. There are a number of ways you could expand on this theme. One is to create additional modifiers such as 'enum', or combine the '=' with the ':' to get ':=' and '=:'. Possibly ':=' could be an enum, and maybe '=:' would be a lambda or local named block of code. I don't particularly like reusing def. I've always thought of 'def' as being short for 'define function' and 'class' for being short for 'define class'. So 'def foo(x): code_block' spells "define function 'foo' that takes args (x), and associate it to code_block. Where 'associate' is the ':'. Reusing either def or class to do something different doesn't seem correct to me. I think it would make python more confusing to those just starting out. But Probably the above consistencies were never thought out in this way, or maybe they were at one time and there was never a need to express it publicly. But I kind of like how it fits together conceptually even if it's just my own way of looking at it. Cheers, Ron (* Except in slicing syntax, which isn't related at all.)
participants (20)
-
Alexander Belopolsky -
Barry Warsaw -
Bruce Leban -
Ethan Furman -
Greg Ewing -
Guido van Rossum -
Haoyi Li -
Jim Jewett -
Martin Morrison -
MRAB -
Ned Batchelder -
Nick Coghlan -
Paul Moore -
Piotr Duda -
Raymond Hettinger -
Ron Adam -
Stefan Drees -
Stephen J. Turnbull -
Steven D'Aprano -
Yuval Greenfield