[HN Gopher] Strict Python function parameters
       ___________________________________________________________________
        
       Strict Python function parameters
        
       Author : todsacerdoti
       Score  : 121 points
       Date   : 2022-01-23 14:15 UTC (8 hours ago)
        
 (HTM) web link (sethmlarson.dev)
 (TXT) w3m dump (sethmlarson.dev)
        
       | eatonphil wrote:
       | I always strongly encourage Python programmers to start flipping
       | through Effective Python to see more like this (I believe it
       | includes this tip as well). Great book covering parts of the
       | language and stdlib you may not remember or may not have heard
       | of.
        
       | livinglist wrote:
       | I'm more in favor of Dart's approach:
       | 
       | void foo(int? num, {String? text}, [List<int> nums]){}
        
       | softwaredoug wrote:
       | I'm curious if anyones found a good solution to Python default
       | args?
       | 
       | For example, this doesn't do what people expect:
       | def foo(bar=Bar())             ...
       | 
       | Because this creates exactly one Bar at function definition time,
       | not a Bar per function call.
       | 
       | Instead what people usually want is:                   def
       | foo(bar=None)             if bar is None:                 bar =
       | Bar()             ...
       | 
       | I hate having to have a block of `if bar is None` at the top of
       | my functions.
       | 
       | I wonder if there's a library that helps make creating functions
       | with default args easier? Or any tips people have?
        
         | peachy_no_pie wrote:
         | What about:                   def foo(Bar=Bar):             bar
         | = Bar()             ...
         | 
         | Pass in the class or any callable and then call it in the body
         | to make an instance.
        
           | ShamelessC wrote:
           | Yeah, this is the correct answer and it works with functions
           | or objects.
           | 
           | This is commonly used to implement logging callbacks e.g.
           | from collections.abc import Callable                  def
           | log_fn(message: str = ''):             print(f'This is your
           | log: {message}')                  def
           | run_read_file(file_to_read:str, log_handler:
           | Callable=log_fn):             contents =
           | open(file_to_read).readlines()             for line in
           | contents:                 log_handler(message=line)
        
           | dragonwriter wrote:
           | The problem with that compared to late-bound parameters is
           | that you have to pass it a callable for non-default uses as
           | well. It's nice sometimes to have a deferred (callable-
           | supplied) default but pass immediate values for non-default.
        
         | IgorPartola wrote:
         | How about a decorator?                   from functools import
         | wraps              def defaults(**default_kwargs):  # Decorator
         | factory             def decorator(func):  # Decorator function
         | @wraps(func)                 def wrapper(*args, **kwargs):  #
         | Actual wrapper                          # Iterate over all
         | default keyword arguments                     for name in
         | default_kwargs.keys():                         # And if not
         | specified in the passed in kwargs use the default
         | kwargs[name] = kwargs.get(name, default_kwargs[name]())
         | # Call the actual function                     return
         | func(*args, **kwargs)                 return wrapper
         | return decorator                  # Note that the default
         | argument value should be a callable to avoid the pitfall with
         | mutable arguments         @defaults(bar=lambda: [1, 2, 3])
         | def foo(bar):             print(bar)              foo()
        
           | zzzeek wrote:
           | typing tools won't know what the above decorator does,
           | without some fancy PEP-612 annotations and I'm not sure
           | pep612 can cover that level of automation. I think intent and
           | all is clear for everyone just having the construction done
           | in the method body for the (IMO, should be) seldom case this
           | is needed
        
             | IgorPartola wrote:
             | Eh. I don't use typing tools for most things because (and I
             | know this isn't a popular opinion even though I believe
             | it's the correct one) they rarely save you from real world
             | problems. They are mostly useful for auto-complete in your
             | IDE, so you have to weigh the time you spend annotating
             | your code to help your IDE auto-complete function
             | signatures vs the time the auto-complete saves you. I think
             | that if you use this as:                   def foo(bar:
             | list):             print(bar)
             | 
             | that type checking will be happy.
        
           | softwaredoug wrote:
           | Yeah, exactly. I just hacked together a similar thing here :)
           | 
           | https://gist.github.com/softwaredoug/bbfa2541d48c685908ebd6a.
           | ..
        
         | edgyquant wrote:
         | Python is my go to language but there are a lot of these little
         | hang ups you only know about once you've pulled your hair out.
         | I don't know if this is one technically though, function
         | definitions are set at interpretation so I think the result
         | here is pretty straightforward even if it's a hang up for some
         | people.
        
           | jrib wrote:
           | I always point people here for a decent write-up of some
           | "gotchas": https://docs.python-
           | guide.org/writing/gotchas/#mutable-defau... .
           | 
           | What is your list of "these little hang ups you only know
           | about once you've pulled your hair out" if you don't mind
           | sharing?
        
         | cabalamat wrote:
         | > I hate having to have a block of `if bar is None` at the top
         | of my functions.
         | 
         | Doing it the if-statement way is the clearest and easiest for
         | other programmers to follow, therefore it is the best way.
        
         | monkeybutton wrote:
         | You can hide the if statements with `bar = bar or Bar() `
        
           | Strilanc wrote:
           | This isn't always safe to do. It can break duck typing,
           | because not all falsy values are interchangeable in all
           | situations. For example, consider how numpy methods often
           | have an optional `out` parameter indicating where to write
           | the output. So suppose you are writing a value producing
           | method that takes an `out: Optional[List[int]] = None` to put
           | the produced values into and you handle the `None` case with
           | `out = out or []`. That's a bug! When the user tries to pass
           | in a list to put things into, and that list happens to be
           | empty, you will instead put outputs into a new list.
           | 
           | Anyways, my point is that I can write `if X is None: X = bla`
           | and be confident it will be correct in almost all situations.
           | For `X = X or Y` there are some nasty corner cases where the
           | distinction between `[]` and `""` and `{}` is somehow
           | important, or the distinction between `is` and `==` is
           | important, or different falsy values have different
           | annotations attached to them, or or or....
        
             | CraneWorm wrote:
             | bar = Bar() if bar is None else bar
        
             | [deleted]
        
             | monkeybutton wrote:
             | All of this is true. Its also weird to me that the
             | evaluation of a boolean expression can return return a
             | value that is neither True nor False (yes I know the value
             | evaluates to true, that's why the trick works).
             | 
             | Which is why I just do `if param is None:... ` and not
             | worry about the few extra lines of code and move on.
             | 
             | The trick does have the nice property of delaying execution
             | for evaluating what the default parameter though. The only
             | other way to do that would involve writing lambdas which is
             | even more code? The sibling comment suggesting a package
             | looks like even more code to write as a user. I think any
             | really elegant fix would have to be done in the language
             | itself.
        
               | hiptobecubic wrote:
               | It's not only not a trick, it's how the language is
               | designed with the intention of doing it like this.
               | 
               | To me it's like complaining that (++a) is a "trick" that
               | lets you use secret temp values that are hard to
               | understand. I mean.. _I guess_, but it's C so it seems
               | like the problem is that you don't like C.
        
         | wheelerof4te wrote:
         | "I hate having to have a block of `if bar is None` at the top
         | of my functions."
         | 
         | Why? This is like someone saying "I don't like declaring my
         | variables in C at the top of the function".
         | 
         | It is a good and readable solution to a somewhat rare problem.
         | Passing mutable objects as default function parameters is a
         | common code smell in Python.
         | 
         | And no, adding a special syntax to an already syntax-heavy
         | language to address this is not good.
        
           | softwaredoug wrote:
           | Because it's harder for the user of the API to know what the
           | default is.
           | 
           | It's boilerplate that distracts from the purpose of the
           | function, this getting in the way of readability.
           | 
           | It's also just more code prone to a programmer screwing up
           | from time to time, despite how simple it is.
        
             | wheelerof4te wrote:
             | Pythonic way to solve this problem would be to make Bar() a
             | component of another class.
             | 
             | Functions are not a good tool for storing stateful objects
             | in Python. That's why classes exist.
        
         | computerfriend wrote:
         | You can do                   def foo(bar=None):             bar
         | = bar or Bar()
         | 
         | if you know that bar can't evaluate to false in edge cases.
        
         | Strilanc wrote:
         | Maybe you could make a function decorator that looked for a
         | special type of default value indicating how to make the
         | default value each time. Similar to `attr.Factory` [1] for
         | `attr.dataclass` annotations.
         | 
         | 1: https://www.attrs.org/en/stable/api.html#attrs.Factory
        
           | softwaredoug wrote:
           | I was thinking this too, if a decorator could make this
           | easier.
           | 
           | I'd also like whatever a solution is to work well with type
           | annotations.
           | 
           | I'm surprised there isn't a pep to improve the situation.
           | (Maybe there is and I haven't found it!)
        
             | hultner wrote:
             | There is, see my comment here:
             | 
             | https://news.ycombinator.com/item?id=30047413
        
           | dragonwriter wrote:
           | > Maybe you could make a function decorator that looked for a
           | special type of default value
           | 
           | If you make the functions to supply late-bound defaults
           | arguments to the decorator, its pretty doable; here's what I
           | just threw together.                   from inspect import
           | signature, Parameter              def
           | with_deferred_defaults(*args, **kwargs):             def
           | decorate(fun):                 parameters =
           | signature(fun).parameters
           | positional_parameters = [parameter for parameter in
           | parameters.values() if parameter.kind in
           | [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]]
           | defaults = list(args)                 kwdefaults = kwargs
           | def wrapper(*args, **kwargs):                     args =
           | list(args)                     if len(args) < len(defaults):
           | args = args + [arg() for arg in defaults[len(args):]]
           | new_kwargs = {k: v() for k, v in kwdefaults.items()}
           | new_kwargs.update(kwargs)                     for i, p in
           | enumerate(positional_parameters):                         if
           | i >= len(args):                             if p.name in
           | new_kwargs:
           | args.extend([None]*(1+i-len(args)))
           | args[i] = new_kwargs[p.name]
           | del new_kwargs[p.name]                             elif
           | p.default is Parameter.empty:
           | raise TypeError('Missing argument: {}'.format(p.name))
           | else:                             if p.name in new_kwargs:
           | del new_kwargs[p.name]                     return fun(*args,
           | **new_kwargs)                 return wrapper
           | return decorate
        
         | dec0dedab0de wrote:
         | For your example I would do foo(Bar()) when I needed to run it
         | without an existing object. Or make foo a method on the Bar
         | class and do Bar().foo()
         | 
         | In general I just try my best to avoid immutable, or dynamic
         | defaults, but sometimes they're necessary and I make that same
         | if block at the top of the function. If I catch myself
         | copy/pasting the same setup to multiple functions, then it
         | might be time for a class or a decorator.
        
         | toyg wrote:
         | Although "if bar is None:" is the explicit form, the common
         | idiomatic one is "if not bar:" (unless it's acceptable for bar
         | to be 0).
        
           | dragonwriter wrote:
           | > unless it's acceptable for bar to be 0
           | 
           | Or an empty list.
           | 
           | Or any other falsish value.
        
         | hultner wrote:
         | There's a PEP suggested for 3.11 handling this
         | 
         | https://www.python.org/dev/peps/pep-0671/
        
           | meowface wrote:
           | Sounds like a good idea. Not really a fan of the suggested
           | syntax, though:                 def fn(s, n=>len(s)):
           | 
           | "=>" just kind of bugs me, for some reason. (I love me some
           | bikeshedding.)
           | 
           | Of the alternatives they offer, ":=" kind of looks nicest to
           | me:                 def fn(s, n:=len(s)):
           | 
           | Though it doesn't really line up with the semantics of the
           | existing walrus operator. Also, as the PEP mentions, it could
           | look confusing when mixed with type annotations.
           | 
           | Semantically, the "?=" alternative might be best, since it
           | kind of makes it clear it's a default, and doesn't conflict
           | with anything else in the language:                 def fn(s,
           | n?=len(s)):
           | 
           | Kind of reads like "present? else = ...".
           | 
           | edit: At this point I'd shift from "might be best" to "almost
           | certainly is best". I'm now a zealous "?=" advocate.
        
             | kortex wrote:
             | I agree. I think python needs _some_ solution to the `def
             | foo(mylist=[])` which is a mistake I see devs make all the
             | time. `foo= >func(a)` is awful. There's too much precedent
             | that `x = f => {}` defines a lambda with x as a parameter.
             | This flips it on its head. `@` would be challenging for the
             | parser. `:=` also does not match the walrus semantics.
             | 
             | `x ?= default` is perfect IMHO. There's already prior art
             | (Makefiles) that this means exactly "present? else = ..."
             | with lazy evaluation of RHS expression.
             | 
             | It also would allow eliminating the "if x is SENTINEL"
             | pattern, where None is a valid non-default argument.
        
               | throwaway775888 wrote:
               | What about .&= for default arguments?, inspired by Ruby.
        
               | meowface wrote:
               | Yeah, the more I look at it, the more I'm a fan of "?=".
               | Kind of seems like the obvious choice at this point.
               | 
               | The only potential downside is that standard, function
               | definition-time default arguments also imply "present?
               | else = ...". But I think of all the options available,
               | this one makes the most sense.
        
               | klyrs wrote:
               | Having mylist stick around is actually useful for making
               | stateful functions. For example, I've often used
               | def foo(..., cache={}):         ...
               | 
               | to implement cached functions, or functions which need an
               | internal cache for whatever reason. Later versions of
               | Python provide an lru cache decorator, but that doesn't
               | help with the internal cache and can be inefficient when
               | you don't want your cache to forget any results.
        
             | softwaredoug wrote:
             | I don't think its bike shedding FWIW, Python traditionally
             | eschews fewer keywords and more obvious operators. So it's
             | a reasonable concern.
        
               | meowface wrote:
               | Yeah, it's probably not bikeshedding when it concerns a
               | very popular programming language's syntax. I just find
               | myself prone to bikeshedding and pedantic naming
               | indecisiveness in general.
        
           | georgyo wrote:
           | The PEP seems entirely focused on bound checking and
           | performing functions on variables in the function definition.
           | 
           | I'm not sure it handles the use case op is talking about.
           | 
           | IE def foo(bar=list()): # this creates a mutable list shard
           | by all calls of the function. Very unlikely something you
           | want.
           | 
           | Maybe the pep would allow for def foo(bar=>list()):
           | 
           | But the pep does not address this use case at all
        
             | meowface wrote:
             | Even though the PEP doesn't explicitly mention mutable
             | default arguments, I'm pretty sure it's intended for this
             | use case, as well. It should address this issue.
             | 
             | >Function parameters can have default values which are
             | calculated during function definition and saved. This
             | proposal introduces a new form of argument default, defined
             | by an expression to be evaluated at function call time.
        
         | aix1 wrote:
         | I agree that it's unfortunate that this is the default
         | behaviour. It's just so unexpected when first encountered.
         | 
         | That said, it is not always desirable that a new object is
         | created each time the function is called without the arg. One
         | obvious example is when the default is an immutable object
         | that's either large or expensive to construct.
        
         | kortex wrote:
         | If this is a class, you can use dataclasses.field with
         | default_factory. However I don't think there is anything
         | analogous for standalone functions.
         | 
         | https://docs.python.org/3/library/dataclasses.html#dataclass...
         | 
         | Functions are in fact objects as well, so there might be some
         | way to do this sort of behavior with decorators.
         | 
         | I would actually contend that "if x is None, x =Bar()" is
         | actually a superior pattern in some/many cases. I used to use
         | "x or Bar()" or "x if x is not None else Bar()" quite a lot,
         | due to its compactness. However I learned that actually
         | sometimes code coverage fails to ensure proper coverage of
         | these ternary assignments. If your default argument is just
         | because you need to initialize a mutable object, ternary works
         | fine. However if the presence or lack of a parameter is an
         | important part of your state space, you probably want explicit
         | coverage.
         | 
         | https://stackoverflow.com/questions/47958166/code-coverage-a...
        
         | coldtea wrote:
         | > _I 'm curious if anyones found a good solution to Python
         | default args?_
         | 
         | Apart from the common solutions (initialize to None and check,
         | etc.) this is exactly what this PEP is about:
         | https://www.python.org/dev/peps/pep-0671/
        
         | tpoacher wrote:
         | While "weird" when first encountered, once you wrap your head
         | around why this happens, then this is a feature, not a bug.
         | 
         | Both in terms of enhancing one's understanding of python's
         | memory model, _and_ in terms of it being an incredibly useful
         | feature, e.g. for introducing static /persistent variables to a
         | function (without the need for global declarations)
         | 
         | I would _not_ want to see this  "fixed"
        
           | remexre wrote:
           | Common Lisp takes expressions for default arguments, and has
           | the "fixed" behavior; when one wants a persistent value, they
           | do either:                   (let ((my-list '(1 2 3)))
           | (defun foo (&optional (l my-list))             (setf (car l)
           | 'hello)             l))
           | 
           | or, more often, just make it a special variable of some sort:
           | (defvar *my-list* '(1 2 3))         (defun foo (&optional (l
           | *my-list*))           (setf (car l) 'hello)           l))
           | 
           | This seems to work fine, and brings attention to "I have an
           | object that supports mutation, and I'm actually going to
           | mutate it," which is an unusual enough case to be worth
           | calling out.
        
             | lispm wrote:
             | You are modifying literal data, which is not a good idea.
        
           | Nihilartikel wrote:
           | I won't bite on the feature/bug argument, but it's enough of
           | a foot-gun that I have tracked down no less than 3 real world
           | bugs relating to it in large python code bases. Usually when
           | people use an empty list literal as default and shit gets
           | real when it retains values on the next call.
           | 
           | When performing Python interviews I usually try to have that
           | behavior come up in the coding question - not to quiz for
           | trivia so much as to get a feeling for adding on battle-scar
           | bonus points.
           | 
           | In practice, for any non plain-ol-data defaults I just have
           | it default to None and handle it in logic.
           | 
           | Totally understand why it may be useful for introducing a
           | persistent store to a function, but as a matter of taste I
           | like to do that in an explicit decorator if needed.
        
           | justinpombrio wrote:
           | As Python is now, this code:                   def
           | myfun(person = Person()): ...
           | 
           | will evaluate `Person()` only once. If you want it to be
           | evaluated every time, you have to say:                   def
           | myfun(person = None):             if person is None: person =
           | Person()             ...
           | 
           | What's being suggested, is to change the language so that the
           | default-arg code:                   def myfun(person =
           | Person()): ...
           | 
           | will evaluate `Person()` on every call. If you wanted it
           | evaluated only once, you would have to say:
           | __default_person = Person()         def myfun(person =
           | __default_person): ...
           | 
           | In either case, it's pretty easy to obtain the other case,
           | and the question is what the best _default_ is. My impression
           | is that  "evaluate every time" is both a lot less surprising
           | and more commonly what you want, so it would be the better
           | default.
        
           | mannykannot wrote:
           | > Then this is a feature, not a bug.
           | 
           | > Both in terms of enhancing one's understanding of python's
           | memory model...
           | 
           | In much the same way as having your company bricked by a
           | buffer-overflow ransomware attack does in the C world? I'm
           | not a big fan of elucidation by gotcha.
           | 
           | > [...and] for introducing static/persistent variables to a
           | function (without the need for global declarations)
           | 
           | Except when you are at risk of someone calling with their own
           | variable(s) that do not have the static-singleton semantics
           | that the default would have.
        
           | armchairhacker wrote:
           | i mean it should at least be clear, because it's not like
           | that in most languages. And there could be 2 forms of syntax
           | for default arguments. singletons or each-call
        
           | chc wrote:
           | This seems pretty iffy for introducing static/persistent
           | variables to a function. I mean, it can work, but it's
           | semantically very confusing. Parameters are part of a
           | function's interface. A global variable would be much better.
        
       | kuang_eleven wrote:
       | I can't see the use of this in almost any code. All of the "bad"
       | examples they gave just seemed perfectly fine, and if they make
       | sense to the user of the library, there's no reason to disallow
       | them.
       | 
       | There _are_ rare cases that actually call for keyword-only and
       | (even more rarely) positional-only arguments, but unless they are
       | _required_ for your usecase, please do not enforce arbitrary
       | argument types on your users.
        
       | nmalaguti wrote:
       | While this is nice in a lot of ways, it also can make it harder
       | for your callers to use more dynamic patterns. You can no longer
       | call it with                   process_data(**kwargs)
       | 
       | and have the data field pulled out of kwargs. Of course you can
       | still do                   data = kwargs.pop("data")
       | process_data(data, **kwargs)
       | 
       | but if you are the only library doing it, it can cause things to
       | break when it breaks programmers expectations.
        
         | masklinn wrote:
         | Not sure that's an issue, since the parameter does not have a
         | name.
         | 
         | Just call it as `*args, **kwargs`. That's something you need to
         | do when forwarding anyway.
        
         | IshKebab wrote:
         | That's a good thing! Using `kwargs` is generally a terrible
         | idea.
        
           | DangitBobby wrote:
           | Please elaborate.
        
             | edgyquant wrote:
             | In a dynamic language it's already hard enough determine
             | what type a given variable is at runtime once the codebase
             | reaches a certain size. When you're passing arbitrary
             | parameters in the form of kwargs that just adds another
             | layer of dynamism on top that can make debugging even more
             | difficult. It definitely has its place but it's generally a
             | good idea to use positional parameters if you know they
             | will be passed.
        
             | spicybright wrote:
             | It's impossible to see what arguments a function expects
             | without reading all the code that uses it.
             | 
             | You can't always trust documentation to be accurate and up
             | to date.
        
         | SethMLarson wrote:
         | Yep, using positional-only parameters makes calling the API
         | tougher when all you've got is a `**kwargs` dictionary. But I
         | think that for well designed APIs you'll be able to avoid the
         | scenario of "having" to pass down arguments this way.
         | 
         | I have an article in the earlier stages about what I call the
         | "keyword arguments blender" and how to avoid it, I think it's a
         | common anti-pattern in lots of Python code (including my own,
         | guilty!)
        
       | Zababa wrote:
       | The "encoding" argument seems like the perfect use case for a
       | strict type, ideally something that works like a sum type, or
       | union types in TS. I think the difference is that in Python
       | unions are unions of types, and not of values. I think
       | "typing.Literal" would be what you can use here in Python.
        
       | teddyh wrote:
       | I agree with everything in the article, but I would like to add
       | one observation:
       | 
       | The article uses as an example of two arguments, a byte string
       | and a string specifying the character encoding of the first
       | argument. Having two arguments so strongly associated with each
       | other as _separate_ arguments is a slight code smell. One should
       | contemplate unifying them into a compound type (like a class
       | instance) which includes both arguments. It's not always
       | reasonable, of course, and may add more complexity than it saves.
       | But one should keep an eye out for this.
        
         | mattarm wrote:
         | Agreed, especially if the related values occur a lot in a
         | program and are thought of as "thing". E.g. a Point or Line
         | class.
         | 
         | I do think this moves the problem of possible argument
         | confusion to the construction of the new type. At some point
         | the association must be expressed in code, and if the
         | association is between two things of the same type the type
         | system can't flag mistakes as errors. A simple data class still
         | helps, even if it doesn't eliminate the problem, because people
         | can more easily remember one Point(x, y) constructor than where
         | "x" and "y" appear in dozens of different functions.
        
         | blip54321 wrote:
         | That tends to turn Python into Java....
        
           | teddyh wrote:
           | Yes, you should not do it in case it doesn't help. But it
           | sometimes does help.
        
         | smitty1e wrote:
         | A class seems heavy. The tuple() is a great way to keep it
         | light.
        
           | teddyh wrote:
           | Use a named tuple. This way, you can turn it into a class if
           | it ever becomes necessary without changing any other code.
        
             | SethMLarson wrote:
             | I'd recommend dataclasses over namedtuples. In almost every
             | case the "iterability" of a namedtuple ends up not being
             | necessary (and can be a footgun!) what you really want is a
             | lightweight namespace with types i.e. dataclasses!
        
               | teddyh wrote:
               | On the other hand, the immutability of a named tuple can
               | be useful.
        
           | wheelerof4te wrote:
           | Yes, but only for compound types with 3-4 values, maximum.
           | 
           | If you need something to store 7-8 different value types,
           | either make a class or reach for named tuple.
           | 
           | I tried using regular tuples as lightweight objects, it does
           | not work.
        
         | SethMLarson wrote:
         | For more complicated cases I would agree, having a class that
         | encapsulates logic that's inter-dependent is good. However I'd
         | caution against reaching for the class approach for scenarios
         | that can be captured by a function call. Python programmers
         | love functions ;)
        
       | dec0dedab0de wrote:
       | I really don't like the idea of a function forcing me to use it a
       | specific way. It's the same reason I don't like type checking at
       | runtime.
       | 
       | I can see the benefit of forcing junior members of my team to use
       | keyword arguments, but I would be so annoyed if a library author
       | did it to me.
       | 
       | Unless there is a technical reason, your callables should just
       | work the same way as all the others.
       | 
       | I can't think of any reason to use positional only arguments, but
       | keyword only arguments would be nice if I had a variable length
       | of positional arguments that get packed, but then wanted to have
       | an optional argument to change some behavior. But even then it's
       | normally better to just have the caller send an iterable.
        
         | DangitBobby wrote:
         | Agreed. This makes some patterns unnecessarily difficult for
         | little gain. I wish they hadn't added these parameter
         | restrictions to the language.
        
       | hprotagonist wrote:
       | i am a huge fan of this style:                 def
       | foo(obvious_thing:type, *, modifier:type, modifier2:type, ...)
       | 
       | Because I find this call incomprehensible:
       | foo(items, 12, "apple")
       | 
       | and this immediately better:                 foo(items, num=12,
       | kind="apple")
       | 
       | and preventing my callers from being able to do the first is a
       | huge win.
        
         | DangitBobby wrote:
         | Forcing your consumers to use a style you like is not a "win",
         | it's a power play.
        
           | jackblemming wrote:
           | This is completely right. On top of this, most modern IDEs
           | can show you implicit parameter names in the function call if
           | you want. It's a feature you can toggle on or off. Don't
           | force people to consume things how you want because you dont
           | use local variables or have a modern IDE.
        
           | hprotagonist wrote:
           | i intend it as both, and i'm aware of that fact.
           | 
           | you call it "power play", i call it "having an API for my
           | libraries"; potato, potato.
        
         | edgyquant wrote:
         | This is definitely how I prefer it as well after watching a
         | talk on YouTube by a python core dev who recommended this for
         | easier readability.
        
         | peteradio wrote:
         | Seems reasonable but when are you actually calling foo like:
         | 
         | foo(items, num=12, kind="apple")
         | 
         | vs:
         | 
         | foo(items, num=num, kind=kind)
         | 
         | ?
         | 
         | Then you might as well get your info from the variable names in
         | the calling method.
        
           | messe wrote:
           | There's been some discussion on that[1], with some suggesting
           | the introduction of syntax analogous to f-strings:
           | foo(items, num=, kind=)         # equivalent to
           | foo(items, num=num, kind=kind)
           | 
           | But as far as I know there hasn't been a formal proposal (I
           | could be wrong, and am happy to be corrected on this).
           | 
           | [1]: https://lwn.net/Articles/818129/
        
             | hprotagonist wrote:
             | that looks nice! not essential, but nice. I like the
             | similar 3.8+ f-string improvement:                 x=5
             | print(f"{x=}")
        
           | hprotagonist wrote:
           | and one day someone goofs and                 foo(items,
           | kind, num)
           | 
           | and then everyone is sad again.
        
           | wheelerof4te wrote:
           | Yes, but keyword arguments are a good fallback option.
           | 
           | They indicate that you are indeed passing the required
           | arguments. So, the                 def foo(val,
           | querry=querry):         ...
           | 
           | doesn't look as funny as you might think.
        
       | rytill wrote:
       | Thank you for sharing. I wish this were the default behavior of
       | positional and keyword arguments rather than requiring a `/, *`
       | in every function definition.
        
       | physicsguy wrote:
       | Weirdly I literally saw this for the first time this week. I've
       | been programming in Python for 8 years!
        
       | toyg wrote:
       | The creation of /, iirc, was initially meant strictly as a way of
       | internally enforcing constraints when interfacing with C
       | libraries. It just so happened that someone dug it out and went
       | "what is this?" and then core devs agreed that it should be
       | surfaced and officially supported.
       | 
       | Personally, I find * useful and / fugly - because positional
       | parameters are very opaque when reading code. They also don't
       | give a chance to the IDE to guess what each parameter is meant
       | for.
        
         | rlayton2 wrote:
         | I'm not a big fan of them either. For teaching beginners, its
         | often "look at the help for this function, but ignore these
         | bits for now", which is quite jarring.
         | 
         | They _are_ useful, but I think sometimes the advanced features
         | run into the basic interfaces a bit, making it weirdly tricky
         | to teach beginners without being confused.
        
           | Siira wrote:
           | Ignoring them is pretty easy. The beginners should learn the
           | art of selective attention anyway.
        
       | nyellin wrote:
       | Slightly off-topic, but everyone writing modern Python should be
       | familiar with Pydantic and similar libraries that use type hints
       | for validation and parsing:
       | 
       | https://pydantic-docs.helpmanual.io/
       | 
       | https://fastapi.tiangolo.com/
       | 
       | https://github.com/tiangolo/typer
       | 
       | We're using Pydantic for Robusta (https://github.com/robusta-
       | dev/robusta) and absolutely love it. You get the best of
       | traditional Python (rapid prototyping and no boilerplate) while
       | still being able to scale your codebase and keep it maintainable.
       | Robusta is the first large project I've written in Python where
       | I'm not encountering type errors at runtime left and right.
        
         | kuang_eleven wrote:
         | As a counterpoint, I cannot stand Pydantic. Type hinting in
         | function class parameters is super useful, but kludges like
         | Pydantic that abuse Python to turn those into runtime checks?
         | Never again.
         | 
         | I am currently finishing up a moderately sized project in
         | FastAPI, and for my next project I will strongly be
         | recommending using Starlette alone.
        
         | xhevahir wrote:
         | I've never understood what I'm supposed to do with things like
         | this or mypy when--as is usually the case--I'm making heavy use
         | of other libraries that don't have type annotations. The
         | examples and tutorials I've seen always show the code importing
         | standard-library modules and nothing else.
        
           | edgyquant wrote:
           | Ideally over time more and more libraries will support typing
           | similar to the way the JS/Typescript ecosystem works.
        
         | wheelerof4te wrote:
         | Not everyone is using Pydantic (nor do I see the reason to) or
         | even type hints, for that matter.
         | 
         | Type hints are great when combined with a good IDE such as
         | Pycharm, but are otherwise useless for Python. Sometimes, they
         | are even missleading. Often they don't cover all the call
         | options.
         | 
         | Complicated function calls should be made with keywords anyway.
         | Leave positional only parameters to obvious functions such as
         | pow() or sqrt().
         | 
         | The speed hit should be minimal. You are using Python, where
         | dot access is a much more bigger performance eater than keyword
         | arg functions.
        
           | macintux wrote:
           | I've certainly avoided a nasty bug or two with Python type
           | hints without an IDE. Not sure why you're so hostile to the
           | idea, the OP didn't say everyone must use them, just that
           | it's helpful to be familiar with them.
        
             | wheelerof4te wrote:
             | I am seemingly hostile to the idea because it is turning
             | Python into somethimg it never should have been.
             | 
             | Python does not have the scale to make typing worthwhile,
             | but people are already making it a required Python skill.
             | 
             | I this feature is optional, then I should not be required
             | to know it, right?
             | 
             | If you want static typing, please use another language that
             | makes use of them to actually speed up the code. Let's keep
             | the docstrings for documentation.
        
               | edgyquant wrote:
               | You aren't required to know it but I don't think it's
               | fair for you to say that typing shouldn't be added just
               | because you don't want to use it. JavaScript had been
               | made better in the form of typescript and I'd love to use
               | a truly typed python as well.
        
               | peterashford wrote:
               | 100% this. I like Python but I'm working on a large
               | inherited code base and type hints on parameters would
               | have made this much easier. Stronger typing would be a
               | good thing imo.
        
           | nyellin wrote:
           | You're right about the performance.
           | 
           | When I mentioned scaling I had in mind other issues with a
           | large codebase - not actual performance at runtime.
           | 
           | For example, before type hints I found it difficult (even
           | with PyCharm and other IDEs) to safely refactor code. I would
           | end up missing specific usages of an old member name and
           | discover it at runtime when the code raised an exception. In
           | the best case scenario I would find it when running tests. In
           | the worst case, in production.
           | 
           | Type hints make this a moot point, at least in real world
           | usage. With type hints the IDE is smart enough to find all
           | references when refactoring.
        
         | tempest_ wrote:
         | We are using FastAPI a bit and it has been a decent experience.
         | 
         | One thing to note about it is tiangolo is pretty content to
         | maintain singular control over his projects which means the
         | project only really advances when he has the time or interest
         | to work on that specific project.
        
           | nyellin wrote:
           | Interesting, thanks. I hadn't thought of that aspect.
        
           | BeefySwain wrote:
           | > One thing to note about it is tiangolo is pretty content to
           | maintain singular control over his projects which means the
           | project only really advances when he has the time or interest
           | to work on that specific project.
           | 
           | Yes, he recently responded to some criticism of this
           | basically saying that he has no real intention to increase
           | the bus factor or permit more community control to his
           | projects, which is obviously his right, but is also something
           | that people should consider when deciding whether or not to
           | go with them.
        
       | amelius wrote:
       | "Strictness" means something else in language design.
       | 
       | https://en.wikipedia.org/wiki/Evaluation_strategy
        
         | Tyr42 wrote:
         | I was curious if they meant strict evaluation, but I clicked
         | through and it was an interesting argument about being strict
         | with what you accept.
        
         | dundarious wrote:
         | Python is already strictly evaluated. It's not ambiguous in
         | that way.
        
           | amelius wrote:
           | Title made me wonder if they changed it or added a new
           | feature around it.
        
       | raffraffraff wrote:
       | I would love to live in a world where articles like this included
       | flashcards that you could instantly add to your Python Anki deck.
       | Instead of what I do: leave the tab open forever or bookmark it
       | (and never see it again).
        
         | throwamon wrote:
         | As someone who suffers from the same problem, something like
         | SuperMemo may be a close enough solution. I've never used it
         | myself, but I know it has features such as "incremental
         | reading" where you add pages to the database and mark pieces of
         | it to read a little at a time.
        
         | pugio wrote:
         | Check out https://withorbit.com - an ongoing research project
         | (and set of tools on github) to embed SRS into any essay/post.
         | If more people adopted it, you might have what you want.
        
       ___________________________________________________________________
       (page generated 2022-01-23 23:01 UTC)