Re: Changing calling sequence
On 16/05/22 1:20 am, 2qdxy4rzwzuui...@potatochowder.com wrote: IMO, classmethods were/are a bad idea (yes, I'm probably in the minority around here, but someone has to be). I don't think class methods are a bad idea per se, but having them visible through instances seems unnecessary and confusing. I suspect that wasn't a deliberate design decision, but just a side effect of using a single class dict for both class and instance things. -- Greg -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 2022-05-15 at 14:44:09 +1000, Chris Angelico wrote: > On Sun, 15 May 2022 at 14:27, dn wrote: > > > > On 15/05/2022 11.34, 2qdxy4rzwzuui...@potatochowder.com wrote: > > > On 2022-05-15 at 10:22:15 +1200, > > > dn wrote: > > > > > >> That said, a function which starts with a list of ifs-buts-and-maybes* > > >> which are only there to ascertain which set of arguments have been > > >> provided by the calling-routine; obscures the purpose/responsibility > > >> of the function and decreases its readability (perhaps not by much, > > >> but varying by situation). > > > > > > Agreed. > > > > > >> Accordingly, if the function is actually a method, recommend following > > >> @Stefan's approach, ie multiple-constructors. Although, this too can > > >> result in lower readability. > > > > > > (Having proposed that approach myself (and having used it over the > > > decades for functions, methods, procedures, constructors, ...), I also > > > agree.) > > > > > > Assuming good names,¹ how can this lead to lower readability? I guess > > > if there's too many of them, or programmers have to start wondering > > > which one to use? Or is this in the same generally obfuscating category > > > as the ifs-buts-and-maybes at the start of a function? > > > > > > ¹ and properly invalidated caches > > > > Allow me to extend the term "readability" to include "comprehension". > > Then add the statistical expectation that a class has only __init__(). Aha. In that light, yeah, in geeral, the more stuff there is, the harder it is to get your head around it. And even if I document the class (or the module), no one makes the time to read (let alone comprehend) the document, which *should* clarify all those things that are hard to discern from the code itself. > > Thus, assuming this is the first time (or, ... for a while) that the > > class is being employed, one has to read much further to realise that > > there are choices of constructor. > > Yeah. I would generally say, though, that any classmethod should be > looked at as a potential alternate constructor, or at least an > alternate way to obtain objects (eg preconstructed objects with > commonly-used configuration - imagine a SecuritySettings class with a > classmethod to get different defaults). I think opening up the class and sifting through its classmethods to find the factory functions is what dn is talking about. Such a design also means that once I have a SecuritySettings object, its (the instance's) methods include both instance and class level methods. IMO, classmethods were/are a bad idea (yes, I'm probably in the minority around here, but someone has to be). The first person to scream "but discoverability" will be severely beaten with a soft cushion. > > Borrowing from the earlier example: > > > > > This would be quite pythonic. For example, "datetime.date" > > > has .fromtimestamp(timestamp), .fromordinal(ordinal), > > > .fromisoformat(date_string), ... > > > > Please remember that this is only relevant if the function is actually a > > module - which sense does not appear from the OP (IMHO). Note that datetime.date is a class, not a module. > > The alternatives' names are well differentiated and (apparently#) > > appropriately named*. [...] > > Continuing the 'have to read further' criticism (above), it could > > equally-well be applied to my preference for keyword-arguments, in that > > I've suggested defining four parameters but the user will only call the > > function with either three or one argument(s). Could this be described > > as potentially-confusing? Potentially. :-) In a well designed *library*, common keywords across multiple functions provide consistency, which is generally good. Even a bit of redundancy can be good for the same reason. OTOH, when there's only one function, and it has a pile of keyword parameters that can only be used in certain combinations, then it definitely can be harder to read/understand/use than separate functions with simpler interfaces. > Yes, definitely. Personally, I'd split it into two, one that takes the > existing three arguments (preferably with the same name, for > compatibility), and one with a different name that takes just the one > arg. That could be a small wrapper that calls the original, or the > original could become a wrapper that calls the new one, or the main > body could be refactored into a helper that they both call. It all > depends what makes the most sense internally, because that's not part > of the API at that point. > > But it does depend on how the callers operate. Sometimes it's easier > to have a single function with switchable argument forms, other times > it's cleaner to separate them. "Easier" and "cleaner" are very often orthogonal. ;-) (Rich Hickey (creator of Clojure) talks a lot about the difference between "easy" and "simple." Arguemnts for and against Unix often involve similar terms.) And "easier" or "cleaner" for whom? The person writing the
Re: Changing calling sequence
On Sun, 15 May 2022 at 14:27, dn wrote: > > On 15/05/2022 11.34, 2qdxy4rzwzuui...@potatochowder.com wrote: > > On 2022-05-15 at 10:22:15 +1200, > > dn wrote: > > > >> That said, a function which starts with a list of ifs-buts-and-maybes* > >> which are only there to ascertain which set of arguments have been > >> provided by the calling-routine; obscures the purpose/responsibility > >> of the function and decreases its readability (perhaps not by much, > >> but varying by situation). > > > > Agreed. > > > >> Accordingly, if the function is actually a method, recommend following > >> @Stefan's approach, ie multiple-constructors. Although, this too can > >> result in lower readability. > > > > (Having proposed that approach myself (and having used it over the > > decades for functions, methods, procedures, constructors, ...), I also > > agree.) > > > > Assuming good names,¹ how can this lead to lower readability? I guess > > if there's too many of them, or programmers have to start wondering > > which one to use? Or is this in the same generally obfuscating category > > as the ifs-buts-and-maybes at the start of a function? > > > > ¹ and properly invalidated caches > > Allow me to extend the term "readability" to include "comprehension". > Then add the statistical expectation that a class has only __init__(). (Confusing wording here: a class usually has far more than just __init__, but I presume you mean that the signature of __init__ is the only way to construct an object of that type.) > Thus, assuming this is the first time (or, ... for a while) that the > class is being employed, one has to read much further to realise that > there are choices of constructor. Yeah. I would generally say, though, that any classmethod should be looked at as a potential alternate constructor, or at least an alternate way to obtain objects (eg preconstructed objects with commonly-used configuration - imagine a SecuritySettings class with a classmethod to get different defaults). > Borrowing from the earlier example: > > > This would be quite pythonic. For example, "datetime.date" > > has .fromtimestamp(timestamp), .fromordinal(ordinal), > > .fromisoformat(date_string), ... > > Please remember that this is only relevant if the function is actually a > module - which sense does not appear from the OP (IMHO). > > The alternatives' names are well differentiated and (apparently#) > appropriately named*. > > > * PEP-008 hobgoblins will quote: > "Function names should be lowercase, with words separated by underscores > as necessary to improve readability. Note the "as necessary". Underscores aren't required when readability is fine without them (see for instance PEP 616, which recently added two methods to strings "removeprefix" and "removesuffix", no underscores - part of the argument here was consistency with other string methods, but it's also not a major problem for readability here). > Variable names follow the same convention as function names." > - but this is a common observation/criticism of code that has been in > the PSL for a long time. > > # could also criticise as not following the Software Craftsmanship/Clean > Code ideal of 'programming to the interface rather than the > implementation' - which we see in PEP-008 as "usage rather than > implementation" > (but please don't ask me how to differentiate between them, given that > the only reason for the different interfaces is the > function's/parameters' implementation!) > > NB usual caveats apply to PEP-008 quotations! Notably here, the caveat that PEP 8 is not a permanent and unchanging document. It is advice, not rules, and not all code in the standard library fully complies with its current recommendations. > Continuing the 'have to read further' criticism (above), it could > equally-well be applied to my preference for keyword-arguments, in that > I've suggested defining four parameters but the user will only call the > function with either three or one argument(s). Could this be described > as potentially-confusing? Yes, definitely. Personally, I'd split it into two, one that takes the existing three arguments (preferably with the same name, for compatibility), and one with a different name that takes just the one arg. That could be a small wrapper that calls the original, or the original could become a wrapper that calls the new one, or the main body could be refactored into a helper that they both call. It all depends what makes the most sense internally, because that's not part of the API at that point. But it does depend on how the callers operate. Sometimes it's easier to have a single function with switchable argument forms, other times it's cleaner to separate them. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 15/05/2022 11.34, 2qdxy4rzwzuui...@potatochowder.com wrote: > On 2022-05-15 at 10:22:15 +1200, > dn wrote: > >> That said, a function which starts with a list of ifs-buts-and-maybes* >> which are only there to ascertain which set of arguments have been >> provided by the calling-routine; obscures the purpose/responsibility >> of the function and decreases its readability (perhaps not by much, >> but varying by situation). > > Agreed. > >> Accordingly, if the function is actually a method, recommend following >> @Stefan's approach, ie multiple-constructors. Although, this too can >> result in lower readability. > > (Having proposed that approach myself (and having used it over the > decades for functions, methods, procedures, constructors, ...), I also > agree.) > > Assuming good names,¹ how can this lead to lower readability? I guess > if there's too many of them, or programmers have to start wondering > which one to use? Or is this in the same generally obfuscating category > as the ifs-buts-and-maybes at the start of a function? > > ¹ and properly invalidated caches Allow me to extend the term "readability" to include "comprehension". Then add the statistical expectation that a class has only __init__(). Thus, assuming this is the first time (or, ... for a while) that the class is being employed, one has to read much further to realise that there are choices of constructor. Borrowing from the earlier example: > This would be quite pythonic. For example, "datetime.date" > has .fromtimestamp(timestamp), .fromordinal(ordinal), > .fromisoformat(date_string), ... Please remember that this is only relevant if the function is actually a module - which sense does not appear from the OP (IMHO). The alternatives' names are well differentiated and (apparently#) appropriately named*. * PEP-008 hobgoblins will quote: "Function names should be lowercase, with words separated by underscores as necessary to improve readability. Variable names follow the same convention as function names." - but this is a common observation/criticism of code that has been in the PSL for a long time. # could also criticise as not following the Software Craftsmanship/Clean Code ideal of 'programming to the interface rather than the implementation' - which we see in PEP-008 as "usage rather than implementation" (but please don't ask me how to differentiate between them, given that the only reason for the different interfaces is the function's/parameters' implementation!) NB usual caveats apply to PEP-008 quotations! So, I agree with you - it comes down to those pernicious 'ifs-buts-and-maybes'. If the interface/parameter-processing starts to obfuscate the function's actual purpose, maybe it can be 'farmed-out' to a helper-function. However, that would start to look very much like the same effort (and comprehension-challenge) as having a wrapper-function! Continuing the 'have to read further' criticism (above), it could equally-well be applied to my preference for keyword-arguments, in that I've suggested defining four parameters but the user will only call the function with either three or one argument(s). Could this be described as potentially-confusing? Given that the OP wouldn't want to have to redefine the existing interface, the next comment may not be applicable - but in the interests of completeness: anyone contemplating such architecture might like to consider "Single-dispatch generic functions" (https://peps.python.org/pep-0443/). At least the decorators signal that there are alternative-choices... -- Regards, =dn -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 2022-05-15 at 10:22:15 +1200, dn wrote: > That said, a function which starts with a list of ifs-buts-and-maybes* > which are only there to ascertain which set of arguments have been > provided by the calling-routine; obscures the purpose/responsibility > of the function and decreases its readability (perhaps not by much, > but varying by situation). Agreed. > Accordingly, if the function is actually a method, recommend following > @Stefan's approach, ie multiple-constructors. Although, this too can > result in lower readability. (Having proposed that approach myself (and having used it over the decades for functions, methods, procedures, constructors, ...), I also agree.) Assuming good names,¹ how can this lead to lower readability? I guess if there's too many of them, or programmers have to start wondering which one to use? Or is this in the same generally obfuscating category as the ifs-buts-and-maybes at the start of a function? ¹ and properly invalidated caches -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 12/05/2022 01.33, Michael F. Stemper wrote: > I have a function that I use to retrieve daily data from a > home-brew database. Its calling sequence is; > > def TempsOneDay( year, month, date ): > > After using it (and its friends) for a few years, I've come to > realize that there are times where it would be advantageous to > invoke it with a datetime.date as its single argument. > > As far as I can tell, there are three ways for me to proceed: > 1. Write a similar function that takes a single datetime.date > as its argument. > 2. Rewrite the existing function so that it takes a single > argument, which can be either a tuple of (year,month,date) > or a datetime.date argument. > 3. Rewrite the existing function so that its first argument > can be either an int (for year) or a datetime.date. The > existing month and date arguments would be optional, with > default=None. But, if the first argument is an int, and > either of month or date is None, an error would be raised. > > The first would be the simplest. However, it is obviously WET > rather than DRY. > > The second isn't too bad, but a change like this would require that > I find all places that the function is currently used and insert a > pair of parentheses. Touching this much code is risky, as well > as being a bunch of work. (Admittedly, I'd only do it once.) > > The third is really klunky, but wouldn't need to touch anything > besides this function. > > What are others' thoughts? Which of the approaches above looks > least undesirable (and why)? Can anybody see a fourth approach? Reading the above, it seems that the options are limited to using positional-arguments only. Because I keep tripping-over my long, grey, beard; I'm reminded that relying upon my/human memory is, um, unreliable (at least in my case). Accordingly, by the time a function's definition reaches three parameters, I'll be converting it to use keyword-arguments as a matter of policy. YMMV! Remember: if keyword arguments are not used (ie existing/legacy code), Python will still use positional logic. Once the function's signature has been changed, we could then add another keyword-parameter to cover the datetime option. That said, a function which starts with a list of ifs-buts-and-maybes* which are only there to ascertain which set of arguments have been provided by the calling-routine; obscures the purpose/responsibility of the function and decreases its readability (perhaps not by much, but varying by situation). Accordingly, if the function is actually a method, recommend following @Stefan's approach, ie multiple-constructors. Although, this too can result in lower readability. Assuming it is a function, and that there are not many alternate APIs/approaches (here we're discussing only two), I'd probably create a wrapper-function which has the sole task of re-stating the datetime whilst calling the existing three-parameter function. The readability consideration here, is to make a good choice of (new) function-name! * Python version >= 10? Consider using match-case construct keyed on parameter-type -- Regards, =dn -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
You probably want something like overload/multiple dispatch. I quick search on PyPI yields a 'multipledispatch' package. I never used, however. On Wed, May 11, 2022 at 08:36:26AM -0700, Tobiah wrote: On 5/11/22 06:33, Michael F. Stemper wrote: I have a function that I use to retrieve daily data from a home-brew database. Its calling sequence is; def TempsOneDay( year, month, date ): After using it (and its friends) for a few years, I've come to realize that there are times where it would be advantageous to invoke it with a datetime.date as its single argument. You could just use all keyword args: def TempsOneDay(**kwargs): if 'date' in kwargs: handle_datetime(kwargs['date']) elif 'year' in kwargs and 'month' in kwargs and 'day' in kwargs: handle_args(kwargs['year'], kwargs['month'], kwargs['day']) else: raise Exception("Bad keyword args") TempsOneDay(date=datetime.datetime.now) TempsOneDay(year=2022, month=11, day=30) -- https://mail.python.org/mailman/listinfo/python-list -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 11/05/2022 14.58, anthony.flury wrote: Why not do : def TempsOneDayDT(date:datetime.date): return TempsOneDay(date.year, date.month, date.day) No repeat of code - just a different interface to the same functionality. Yeah, a one-line wrapper around the original function seems a lot simpler that any of my ideas. I think that I'll even use the name from your example. Thanks to all who posted, as well as the many lurkers who support me in email. -- Michael F. Stemper Economists have correctly predicted seven of the last three recessions. -- https://mail.python.org/mailman/listinfo/python-list
RE: Changing calling sequence
>>def TempsOneDay(*dateComponents): >>if len(dateComponents) == 3: >>year, month, date = dateComponents >>elif len(dateComponents) == 1 and isinstance(dateComponents[0], >> datetime.date): >>year, month, date = (dateComponents[0].year, dateComponents[0].month, >> dateComponents[0].day) >>else: >>raise Exception("Error message here") > >|>>> help( TempsOneDay ) >|Help on function TempsOneDay in module __main__: >| >|TempsOneDay(*dateComponents) Then just add an appropriate docstring. >>> def TempsOneDay(*dateComponents): ... """Can be called either with 3 arguments: year, month, day ...or with a single datetime.date object""" ... if len(dateComponents) == 3: ... year, month, date = dateComponents ... elif len(dateComponents) == 1 and isinstance(dateComponents[0], datetime.date): ... year, month, date = (dateComponents[0].year, dateComponents[0].month, dateComponents[0].day) ... else: ... raise Exception("Error message here") ... >>> help(TempsOneDay) Help on function TempsOneDay in module __main__: TempsOneDay(*dateComponents) Can be called either with 3 arguments: year, month, day or with a single datetime.date object >>> -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
Why not do : def TempsOneDayDT(date:datetime.date): return TempsOneDay(date.year, date.month, date.day) No repeat of code - just a different interface to the same functionality. -- Original Message -- From: "Michael F. Stemper" To: python-list@python.org Sent: Wednesday, 11 May, 22 At 14:33 Subject: Changing calling sequence I have a function that I use to retrieve daily data from a home-brew database. Its calling sequence is; def TempsOneDay( year, month, date ): After using it (and its friends) for a few years, I've come to realize that there are times where it would be advantageous to invoke it with a datetime.date as its single argument. As far as I can tell, there are three ways for me to proceed: 1. Write a similar function that takes a single datetime.date as its argument. 2. Rewrite the existing function so that it takes a single argument, which can be either a tuple of (year,month,date) or a datetime.date argument. 3. Rewrite the existing function so that its first argument can be either an int (for year) or a datetime.date. The existing month and date arguments would be optional, with default=None. But, if the first argument is an int, and either of month or date is None, an error would be raised. The first would be the simplest. However, it is obviously WET rather than DRY. The second isn't too bad, but a change like this would require that I find all places that the function is currently used and insert a pair of parentheses. Touching this much code is risky, as well as being a bunch of work. (Admittedly, I'd only do it once.) The third is really klunky, but wouldn't need to touch anything besides this function. What are others' thoughts? Which of the approaches above looks least undesirable (and why)? Can anybody see a fourth approach? -- Michael F. Stemper This post contains greater than 95% post-consumer bytes by weight. -- https://mail.python.org/mailman/listinfo/python-list <https://mail.python.org/mailman/listinfo/python-list> -- Anthony Fluryanthony.fl...@btinternet.com -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 2022-05-11, David Raymond wrote: > Maybe not the prettiest, but you could also define it like this, > which also wouldn't require changing of any existing calls or the > main body of the function past this if block. > > def TempsOneDay(*dateComponents): > if len(dateComponents) == 3: > year, month, date = dateComponents > elif len(dateComponents) == 1 and isinstance(dateComponents[0], > datetime.date): > year, month, date = (dateComponents[0].year, dateComponents[0].month, > dateComponents[0].day) > else: > raise Exception("Error message here") That would be my preference were I reading the code. It makes it quite clear that there are two completely separate signatures. I think I would be a little confused by the 2nd and 3rd values with default values — the implication would be that I can supply a datetime object as the first argument and then additional month and date values in the 2nd and 3rd args. You could try to explain it with a comment, but I tend to ignore comments... -- https://mail.python.org/mailman/listinfo/python-list
RE: Changing calling sequence
>> I have a function that I use to retrieve daily data from a >> home-brew database. Its calling sequence is; >> >> def TempsOneDay( year, month, date ): >> >> After using it (and its friends) for a few years, I've come to >> realize that there are times where it would be advantageous to >> invoke it with a datetime.date as its single argument. > >You could just use all keyword args: > >def TempsOneDay(**kwargs): > > if 'date' in kwargs: > handle_datetime(kwargs['date']) > elif 'year' in kwargs and 'month' in kwargs and 'day' in kwargs: > handle_args(kwargs['year'], kwargs['month'], kwargs['day']) > else: > raise Exception("Bad keyword args") > >TempsOneDay(date=datetime.datetime.now) > >TempsOneDay(year=2022, month=11, day=30) > Maybe not the prettiest, but you could also define it like this, which also wouldn't require changing of any existing calls or the main body of the function past this if block. def TempsOneDay(*dateComponents): if len(dateComponents) == 3: year, month, date = dateComponents elif len(dateComponents) == 1 and isinstance(dateComponents[0], datetime.date): year, month, date = (dateComponents[0].year, dateComponents[0].month, dateComponents[0].day) else: raise Exception("Error message here") -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 2022-05-11 at 08:33:27 -0500, "Michael F. Stemper" wrote: > I have a function that I use to retrieve daily data from a > home-brew database. Its calling sequence is; > > def TempsOneDay( year, month, date ): > > After using it (and its friends) for a few years, I've come to > realize that there are times where it would be advantageous to > invoke it with a datetime.date as its single argument. > > As far as I can tell, there are three ways for me to proceed: > 1. Write a similar function that takes a single datetime.date >as its argument. > 2. Rewrite the existing function so that it takes a single >argument, which can be either a tuple of (year,month,date) >or a datetime.date argument. > 3. Rewrite the existing function so that its first argument >can be either an int (for year) or a datetime.date. The >existing month and date arguments would be optional, with >default=None. But, if the first argument is an int, and >either of month or date is None, an error would be raised. > > The first would be the simplest. However, it is obviously WET > rather than DRY. It's also the least disruptive to existing code and tests, and the most clear to readers (whether or not they're familiar with said existing code). What pieces, exactly, do you think you would repeat, especially after you extract the common logic into a new function that should be simpler than either API-level function. -- https://mail.python.org/mailman/listinfo/python-list
Re: Changing calling sequence
On 5/11/22 06:33, Michael F. Stemper wrote: I have a function that I use to retrieve daily data from a home-brew database. Its calling sequence is; def TempsOneDay( year, month, date ): After using it (and its friends) for a few years, I've come to realize that there are times where it would be advantageous to invoke it with a datetime.date as its single argument. You could just use all keyword args: def TempsOneDay(**kwargs): if 'date' in kwargs: handle_datetime(kwargs['date']) elif 'year' in kwargs and 'month' in kwargs and 'day' in kwargs: handle_args(kwargs['year'], kwargs['month'], kwargs['day']) else: raise Exception("Bad keyword args") TempsOneDay(date=datetime.datetime.now) TempsOneDay(year=2022, month=11, day=30) -- https://mail.python.org/mailman/listinfo/python-list
Changing calling sequence
I have a function that I use to retrieve daily data from a home-brew database. Its calling sequence is; def TempsOneDay( year, month, date ): After using it (and its friends) for a few years, I've come to realize that there are times where it would be advantageous to invoke it with a datetime.date as its single argument. As far as I can tell, there are three ways for me to proceed: 1. Write a similar function that takes a single datetime.date as its argument. 2. Rewrite the existing function so that it takes a single argument, which can be either a tuple of (year,month,date) or a datetime.date argument. 3. Rewrite the existing function so that its first argument can be either an int (for year) or a datetime.date. The existing month and date arguments would be optional, with default=None. But, if the first argument is an int, and either of month or date is None, an error would be raised. The first would be the simplest. However, it is obviously WET rather than DRY. The second isn't too bad, but a change like this would require that I find all places that the function is currently used and insert a pair of parentheses. Touching this much code is risky, as well as being a bunch of work. (Admittedly, I'd only do it once.) The third is really klunky, but wouldn't need to touch anything besides this function. What are others' thoughts? Which of the approaches above looks least undesirable (and why)? Can anybody see a fourth approach? -- Michael F. Stemper This post contains greater than 95% post-consumer bytes by weight. -- https://mail.python.org/mailman/listinfo/python-list