Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
necrotic
Aug 2, 2005
I owe my brother big time for this!
Use while true and set the answer at the top of the loop. As written you only read the input once.

Adbot
ADBOT LOVES YOU

ironypolice
Oct 22, 2002

Also you can use a break statement to exit a loop https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops.

necrotic
Aug 2, 2005
I owe my brother big time for this!
The break is already in the right spot, but an input other than Y will not reach it and loop forever without requesting a new input.

a foolish pianist
May 6, 2007

(bi)cyclic mutation

You can also do it recursively:

pre:
def cat_loop_recursive(input=None):
    if input == 'Y':
        return 'yay, cat!'
    else:
        print "Do you pet the cat?"
        user_input = get_input()
        return cat_loop_recursive(input=user_input)

xtal
Jan 9, 2011

by Fluffdaddy
You can in this case but I wouldn't get into the habit of using recursion in Python because it defaults to 1k maximum iterations due to not having tail call optimization

Dancer
May 23, 2011
Stop being cruel to the poor newbie you guys

a foolish pianist
May 6, 2007

(bi)cyclic mutation

That was a bit ridiculous. Really, you just want the exit condition in the loop definition:

pre:
cat_petted = False #boolean to keep track of whether the cat has been petted yet
while not cat_petted:
    ask for input and set cat_petted appropriately

a foolish pianist fucked around with this message at 19:36 on Aug 28, 2019

xtal
Jan 9, 2011

by Fluffdaddy

Dancer posted:

Stop being cruel to the poor newbie you guys

The first reply was exactly what to do lol

To be more clear, the `answer = input...` thing is outside the while loop, so it's only run once. So your `while answer` thing is always referring to the initial input as `answer` never changes. What you want is `while True:` followed by `answer = input...` which is how Python does a do-while loop.

a foolish pianist
May 6, 2007

(bi)cyclic mutation

I really prefer a control variable set ahead of time to a while True: ... break()

pre:
finish_condition = False
while not finish_condition:
    do_stuff()
    if should_exit():
        finish_condition=True

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
I’m making a toy script to check the git repo status of all the repos I have on my computer. Right now I’m just using subprocess.run and doing some regex on stdout to check things like if I’m behind or have new files or whatever.

Is there an easier way to do this with a package and which one?

Master_Odin
Apr 15, 2010

My spear never misses its mark...

ladies

Boris Galerkin posted:

I’m making a toy script to check the git repo status of all the repos I have on my computer. Right now I’m just using subprocess.run and doing some regex on stdout to check things like if I’m behind or have new files or whatever.

Is there an easier way to do this with a package and which one?
https://github.com/gitpython-developers/GitPython is the one I normally use when doing stuff with Git.

Wallet
Jun 19, 2006

a foolish pianist posted:

I really prefer a control variable set ahead of time to a while True: ... break()

There are more or less infinite ways to do this but if you just put it in a function you can hide it somewhere and never look at your dirty while True loop:

Python code:
def pet() -> str:
    while True:
         answer = str(input("U pet cat Y/N?"))
         if answer in ['Y', 'N']:
            return answer


if __name__ == '__main__':
    if len(sys.argv) == 1:
        cat = pet()

Wallet fucked around with this message at 00:03 on Aug 30, 2019

necrotic
Aug 2, 2005
I owe my brother big time for this!
Given the "problem" requires repeating the question unless "Y" is given, and having different responses for "N" vs any other character, that doesn't seem like a great approach...

edit: I'm pretty sure you just wanted to give an example of the approach but it totally fails for the "problem" presented.

KICK BAMA KICK
Mar 2, 2009

Here's an example of exactly the kind of thing I get stumped by and whatever decision I make I eventually regret so I'll just ask this before painting myself into some dumb corner I'll be furiously refactoring a week from now: is it weird to write a utility function to grab poo poo from the Internet (implemented with requests, of course) with logging and exception handling to reuse wherever I would do that in my code, throwing a user-defined exception instead of RequestException? I can't tell if this is straightforward DRY or overengineering that will end up with me adding parameters as I find more use cases and just unnecessarily replicating the requests API. Like, this:
Python code:
def get_url(url: str, timeout: int = 60) -> bytes:
    _logger.debug(f'Retrieving {url}...')
    try:
        response = requests.get(url, timeout=timeout)
    except requests.RequestException as error:
        msg = f'Exception encountered while attempting to download {url}:'
        _logger.exception(msg)
        raise InternetError from error  # just an empty subclass of RuntimeError
    else:
        code = response.status_code
        if 200 <= code <= 300:
            return response.content
        else:
            msg = f'Download of {url} failed with HTTP code: {code}, reason: {response.reason}'
            _logger.error(msg)
            raise InternetError
One useful thing I can imagine doing -- and have done, in a much dumber manner, in a previous version of this code, is evaluating the original exception and subclassing my custom exception to distinguish a local, transient problem (so try again whenever) or one that requires further examination (maybe backburner whatever needed that download). But I'm really not sure -- I feel like I might not have ever thought to do this if it was just standard library code and requests is as close as a third-party package gets -- hypothetically it's an implementation detail the rest of my code could do without knowing but really I'm never gonna swap it out for something else.

(Incidentally, this is the first code I've gotten to use f-strings in and they own, feels very Pythonic -- another plus of Docker, like we were talking about earlier, I can easily use 3.7 without giving a poo poo what's in the repos for the host OS.)

Solumin
Jan 11, 2013
I have no real advice but I'm glad you're using type annotations. :kiddo:

breaks
May 12, 2001

Wrapping requests (or anything) up can be useful if there's stuff you need to do every time that it's not already doing. Without further context, I don't see much advantage of what you've written there over enabling the logging that requests/it's dependencies already do, putting a response.raise_for_status() after your get, and catching the already thrown exceptions wherever if need be. I guess there is potentially some different behavior for 3xx status codes, but those are redirects not failures, and requests follows them by default, so I don't think you will hit that else branch in too many situations where raise_for_status wouldn't raise an exception. I guess a literal 300 response might result in some different behavior but I don't think I've ever actually seen one, I have no idea what requests does with it.

breaks
May 12, 2001

Also my man bromplicated up there is already on 3.8 while yall still on 2.7, just a couple typos is all.

Banjo Bones
Mar 28, 2003

necrotic posted:

Use while true and set the answer at the top of the loop. As written you only read the input once.

xtal posted:

The first reply was exactly what to do lol

To be more clear, the `answer = input...` thing is outside the while loop, so it's only run once. So your `while answer` thing is always referring to the initial input as `answer` never changes. What you want is `while True:` followed by `answer = input...` which is how Python does a do-while loop.

Thank you guys. I knew it was something really simple.

Wallet
Jun 19, 2006

necrotic posted:

Given the "problem" requires repeating the question unless "Y" is given, and having different responses for "N" vs any other character, that doesn't seem like a great approach...

edit: I'm pretty sure you just wanted to give an example of the approach but it totally fails for the "problem" presented.

I was being lazy (and still am), but you get the idea:
Python code:
def interact(prompt: str, valid: list, special: dict) -> str:
    while True:
        answer = str(input(prompt))
        if answer in special:
            print(special[answer])
        if answer in valid:
            return answer


if __name__ == '__main__':
    if len(sys.argv) == 1:
        cat = interact('U pet cat Y/N', ['Y'], {'Y': "Cat purrs.", 'N': "What kind of poo poo doesn't pet a cat?"})
If I'm writing a thing where you interact with a cat, then I probably want a generic function for cat interactions rather than writing a new input loop for every interaction I want you (to be forced) to have with the cat.

Wallet fucked around with this message at 13:44 on Aug 30, 2019

KICK BAMA KICK
Mar 2, 2009

breaks posted:

Wrapping requests (or anything) up can be useful if there's stuff you need to do every time that it's not already doing. Without further context, I don't see much advantage of what you've written there over enabling the logging that requests/it's dependencies already do, putting a response.raise_for_status() after your get, and catching the already thrown exceptions wherever if need be. I guess there is potentially some different behavior for 3xx status codes, but those are redirects not failures, and requests follows them by default, so I don't think you will hit that else branch in too many situations where raise_for_status wouldn't raise an exception. I guess a literal 300 response might result in some different behavior but I don't think I've ever actually seen one, I have no idea what requests does with it.
Thanks for the input -- I did quickly c/p that from some old code when I understood HTTP status codes even less than I do now (still barely), so point taken on the 3xx-es and thanks for introducing me to raise_for_status. I'll play around with the logging a little bit and will proceed keeping this stuff inline and can always factor it out to a function like the above later. Thanks again!

Solumin posted:

I have no real advice but I'm glad you're using type annotations. :kiddo:
Love 'em, they're automatic for me now!

Dominoes
Sep 20, 2007

Type annotations [rich type systems in general] catch classes of errors that would otherwise show up as surprises during execution. Whether it's a compiler, checker like MyPy, or IDE like PyCharm. Unfortunately, ergonomics are awkward when using non-typed dependencies.

Dominoes fucked around with this message at 06:09 on Aug 31, 2019

Dancer
May 23, 2011
A random-ish question that's proving itself surprisingly hard to google. I had a class called Command, that was giving all sorts of weird errors. When I was initializing it, with a bunch of arguments, it failed because for some reason it was using __call__ (which I hadn't even written for it) instead of __init__. After improvising a __call__ to try to make it work it was raising an error that __call__ had the wrong number of arguments.

This all got fixed when I just renamed it to Commandx, leading me to believe that there's some built-in python thing titled Command. But, as you might imagine, typing "python command" and other similar things isn't being helpful :v:. Is Command a thing in Python?

mr_package
Jun 13, 2000

Dancer posted:

A random-ish question that's proving itself surprisingly hard to google. I had a class called Command, that was giving all sorts of weird errors. When I was initializing it, with a bunch of arguments, it failed because for some reason it was using __call__ (which I hadn't even written for it) instead of __init__. After improvising a __call__ to try to make it work it was raising an error that __call__ had the wrong number of arguments.

This all got fixed when I just renamed it to Commandx, leading me to believe that there's some built-in python thing titled Command. But, as you might imagine, typing "python command" and other similar things isn't being helpful :v:. Is Command a thing in Python?
Are you using distutils? There's a Command class in distutils.cmd and it is imported in cpython/Lib/distutils/core.py

xtal
Jan 9, 2011

by Fluffdaddy
Another thing you can do is, before you define your Command class, do this:

Python code:
import inspect
inspect.getmodule(Command)
This will return (so you may need to print the result) the location the class is defined

i vomit kittens
Apr 25, 2019


Does anyone have any recommendations for a good 2D drawing library? I'm using Pillow right now but it's kind of limited as far as shapes go. Aggdraw is pretty much exactly what I'm looking for, but it's Python 2 only.

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

i vomit kittens posted:

Does anyone have any recommendations for a good 2D drawing library? I'm using Pillow right now but it's kind of limited as far as shapes go. Aggdraw is pretty much exactly what I'm looking for, but it's Python 2 only.

I don't really have much experience in the area, but pycairo or maybe even pygame. Both things I've used to draw stuff before...

CarForumPoster
Jun 26, 2013

⚡POWER⚡

i vomit kittens posted:

Does anyone have any recommendations for a good 2D drawing library? I'm using Pillow right now but it's kind of limited as far as shapes go. Aggdraw is pretty much exactly what I'm looking for, but it's Python 2 only.

I know almost nothing about this but the free vector drawing software Inkscape seems to use python for extensions. There may be an interface there that allows you rather robust software with python.

i vomit kittens
Apr 25, 2019


I'll take a look at those. What I'm trying to do (and having trouble with in Pillow) is make a box for text that is somewhat form fitting to the text. Also I need an arrow going from the text box to the character saying it (it's a comic) and Pillow's arc objects for some reason don't have an option to control their thickness.

I'm making a comic generator similar to what the Discord bot "Sepatpus" does. It reads the last X messages in a chat and generates a comic of them with random characters/backgrounds taken from Microsoft Comic Chat. I wanted to port this functionality to GroupMe but already have a functioning bot written in Python and don't like Go much so I decided to do it myself. At first I was just trying to port the code over 1:1 but that wasn't working so well so I just started from scratch.

It doesn't do the text part yet, since that's what I'm figuring out. Right now it just generates the panels, backgrounds, and characters. It also doesn't look too great when you have > 8 messages or one person who says > 2 things in a row, so those are other things I need to sort out afterwards.

i vomit kittens fucked around with this message at 19:10 on Sep 30, 2019

QuarkJets
Sep 8, 2008

I use qt for gui design. It draws all kinds of shapes very well! And it has the other features you're looking for. I don't know whether it's the best tool for the job but it would certainly work

dbcooper
Mar 21, 2008
Yams Fan
Don't have a Python question at the moment (but will add type annotations to the code I'm currently working on), but had some input on Windows 10 and Docker / VirtualBox discussion on the last few pages.

You can create a separate Windows 10 boot option w/ Hyper-V disabled for when you want/need to run VirtualBox. Granted, you have to reboot, but for my use case it was a pretty easy and clean solution using bcdedit:

https://stackoverflow.com/a/43843318 [1]

You may need to hold down [Shift] when clicking Restart to get the boot menu the first time? I seem to get the "choose boot option" screen every time I reboot now. YMMV.


[1] Alternate instructions: https://blogs.technet.microsoft.com/francescovb/2016/11/22/dual-boot-with-no-hyper-v/

Banjo Bones
Mar 28, 2003

Wallet posted:

Python code:
def interact(prompt: str, valid: list, special: dict) -> str:
    while True:
        answer = str(input(prompt))
        if answer in special:
            print(special[answer])
        if answer in valid:
            return answer


if __name__ == '__main__':
    if len(sys.argv) == 1:
        cat = interact('U pet cat Y/N', ['Y'], {'Y': "Cat purrs.", 'N': "What kind of poo poo doesn't pet a cat?"})
If I'm writing a thing where you interact with a cat, then I probably want a generic function for cat interactions rather than writing a new input loop for every interaction I want you (to be forced) to have with the cat.

Can anyone talk more about this?
I wrote a Fridge script, where you put different categories of food in a fridge and at the end it lists them back to you.

I wrote it in a way where it defines a new function for each food category. Is there a way I could write one function to handle all the different food types?

Solumin
Jan 11, 2013
code:
def store_food(food_type: Food, quantity: int):
    # whatever you do to store food
Food should be its own class/enum type, but you would probably just use a string and store the food name instead.

Banjo Bones
Mar 28, 2003

I started writing it as a Fridge class, but got stuck doing that because I kept getting NameError where the instance of fridge I was making was calling lists instead of strings or integers.

Instead I just wrote a series of functions and that was much easier.

Most other people's code I see seem to just use functions. When is a good time to use Classes vs. Functions?

Solumin
Jan 11, 2013
You're going to get a number of answers, I think. Which is good -- programming is problem solving, and people solve problems in different ways.

Functions are purely behavior: they take some input, do some stuff to it, and possibly produce some output.
Classes combine behavior with state. They encapsulate some data and the functions that operate on that data.

Your Fridge is a good example: it encapsulates the contents of the fridge (data) with functions that affect the contents of the fridge. A Fridge class might have store() and remove() methods, for example.
You could do those as standalone functions, of course:

code:
fridge: Dict[str, int] = {}

def store(food: str, quantity: int = 1):
  fridge[food] = quantity

def remove(food: str, quantity: int = 1):
  fridge[food] -= quantity

def main():
  store('apple', 5)
(Overly simplified: I don't check if the fridge already contains the food, or if you're removing more items than are in the fridge.)

This works fine, but what happens if you need more than one Fridge? Well, one way is to make the functions take a Fridge as an argument:

code:
Fridge = Dict[str, int]  # Type alias to make writing the functions easier

def store(fridge: Fridge, food: str, quantity: int = 1):
  fridge[food] = quantity
# And so on

def main():
  fridge = {}
  store(fridge, 'apples', 5)
  other_fridge = {}
  store(other_fridge, 'oranges', 4)
But at this point, you basically have a class:

code:
class Fridge:
  def __init__(self):
    self._storage: Dict[str, int] = {}

  def store(self, food: str, quantity: int = 1):
    self._storage[food] = quantity

def main():
  fridge = Fridge()
  fridge.store("apples", 5)
  other_fridge = Fridge()
  other_fridge.store("oranges", 4)
Now all the data and functions that apply to Fridges are contained within the Fridge class. If we need to add more behavior, we can just modify the class.

As our code gets more complicated, classes help us keep everything organized. It's easier to import modules that use classes, it's easier to use class objects instead of the separate data/functions, and it lets you reuse names of things. (What if we wanted a Wardrobe class that also stored a quantity of things? We'd like to have store() and remove() methods!)


My personal way of thinking about this is that classes store data and functions modify data. (But I'm at heart a functional programmer.)

Dominoes
Sep 20, 2007

Here's the first functional release of the package manager I posted about earlier. After feedback from last time, I think the readme does a better explaining its purpose, ie why I built it when pip/venv/poetry/pipenv and Conda exist.

unpacked robinhood
Feb 18, 2013

by Fluffdaddy
This question is more suited for a general "how to talk with serial peripherals" conversation than Python itself but I'll give it a shot:

I'm trying to do work out a thing with an analog modem that picks up when the someone calls on a land line, but my code isn't reliable.

The program runs ok until the second to last line, but then it frequently misses the RING word. Calling a second time usually goes through , and then it finds the caller number just fine.
Minicom shows the modem behaving as expected so I'm don't suppose it's a hardware issue.

I'd appreciate ideas or pointers to address this.

e: I use pySerial

unpacked robinhood fucked around with this message at 17:26 on Sep 4, 2019

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Dominoes posted:

Here's the first functional release of the package manager I posted about earlier. After feedback from last time, I think the readme does a better explaining its purpose, ie why I built it when pip/venv/poetry/pipenv and Conda exist.

quote:

Multiple versions of a dependency can be installed, allowing resolution of conflicting sub-dependencies, and using the highest version allowed for each requirement.

How did you achieve this?

Dominoes
Sep 20, 2007

Renaming imports, both in the dependency and its parent. It's currently fragile: Packages that include relative imports in compiled code don't work, and publishing a package relying on this wouldn't work properly unless its user also used something capable of handling this.

I'm looking for suggestions on how to improve this, with the default answer being it's not possible to do correctly.

Wallet
Jun 19, 2006

bromplicated posted:

I wrote it in a way where it defines a new function for each food category. Is there a way I could write one function to handle all the different food types?

Solumin gave a great overview of why you might want to use classes and what you might use them for. I don't know exactly what your script did, but to give you a simple version of putting different categories of food into a fridge and then listing everything that was added back at the end:

Python code:
from typing import NamedTuple

req_cats = ['Vegetables', 'Fruits', 'Dairy', 'Meat']
food_cat = NamedTuple('food_cat', [('name', str), ('foods', list)])


def add_food(c: str) -> list:
    food = []
    print(f'==Adding {c}==\nEnter "list" for a list of what you have already added to this category.')
    while True:
        answer = str(input('Enter a food to add (or enter "Done" to finish):\n'))
        if answer.lower() == "list":
            if len(food) < 1:
                print("You haven't added anything yet.")
            else:
                print('\n'.join(food))
        elif answer.lower() == "done":
            return food
        else:
            food.append(answer)


if __name__ == '__main__':
        fridge = []
        for cat in req_cats:
            fridge.append(food_cat(cat, add_food(cat)))
        print("==Fridge Contents==")
        for cat in fridge:
            if len(cat.foods) > 0:
                print(f'={cat.name}=')
                print('\n'.join(cat.foods))
You could add a count for each item fairly easily, but if you want to add much more complexity than that I would personally go the class route.

Wallet fucked around with this message at 01:03 on Sep 5, 2019

Adbot
ADBOT LOVES YOU

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Dominoes posted:

Renaming imports, both in the dependency and its parent. It's currently fragile: Packages that include relative imports in compiled code don't work, and publishing a package relying on this wouldn't work properly unless its user also used something capable of handling this.

I'm looking for suggestions on how to improve this, with the default answer being it's not possible to do correctly.

Does it handle imports done with importlib? That's pretty common, so if it doesn't you should probably make a note in the readme or something.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply