[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)