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
QuarkJets
Sep 8, 2008

Yeah you might examine your software design if you have tons of variables that need to be compared to None and possibly be set to a not-None default, that's odd. Why are you using None so often?

Adbot
ADBOT LOVES YOU

QuarkJets
Sep 8, 2008

Falcon2001 posted:

Yeah, I know this is moving into antipattern territory. This portion is entirely internal, and none of these objects exit to my dependencies. In fact, a bit part of what I'm doing is trying to generate safe, fully generated objects to output by catching any places where things are null'd out during generation of those objects.

I think the proper way to do this would be with more exceptions and error handling instead of 'Optional[outputObject]', but this isn't something I'm shoving outside of this module at least.

Edit:

To expand on this, I guess I can just lay out what I'm doing and if I'm being an idiot, tell me.
Python code:
component_one = get_c1()
component_two = get_c2()
component_three = get_c3()

if not (c1 and c2 and c3):
	log("Cannot make this object!")
	return None

merged_output_object = build_merged_output_object(c1,c2,c3)

return merged_output_object
This is obviously pretty abstracted, but is a high level summary of it.

That seems basically fine, but it kind of depends on what this function is used for.

c1, c2, and c3 can be thought of as inputs to your application; you have some external interface that provides these values. That's where you should handle these variables containing None. If a value is None, return a default object instead. For instance if these variables are supposed to be lists, then just return an empty list if you receive None. Then the rest of your code doesn't have to worry about whether these values are None, because you already handled that situation

Seventh Arrow
Jan 26, 2005

I have a data engineer interview with Amazon tomorrow and I'm like 99% sure I'm going to bomb whatever python (and probably SQL) test they're going to give me lol. Even some of the easy stuff on leetcode causes my brain to bluescreen.

I think I'm going to just treat it as a learning experience and not sweat it. I learned lots of leadership principles though! :shepicide:

12 rats tied together
Sep 7, 2006

Falcon2001 posted:

This is obviously pretty abstracted, but is a high level summary of it. Double edit: this doesn't even contain what I asked about before. I might be an idiot. Anyway, feel free to critique this too!

At some point you need to ingest and handle the ugliness of reality, that part doesn't have to be particularly clean or clever or elegant as long as it works and its understandable. You're best equipped out of all of us here to write that part. :)

The important thing is that you fully handle it and provide a clean contract with the rest of your codebase, and it sounds like you have a good handle on that.

I would add that, in general ,I try not to rely on like "whatever I return will respond to __get_item__", I generally prefer creating a class that relates to a business logic concept and has methods with named parameters and docstrings that try to map english explanations of the business logic concepts to the programming language mechanisms for interacting with those constructs.

I don't think that's considered very pythonic, but python is at least partially an OO language so I feel like it's fine to rely on OO design principles when it makes sense.

The March Hare
Oct 15, 2006

Je rêve d'un
Wayne's World 3
Buglord

QuarkJets posted:

Yeah you might examine your software design if you have tons of variables that need to be compared to None and possibly be set to a not-None default, that's odd. Why are you using None so often?

I've been in OPs position before when writing systems to ingest absolutely insane API data.

I'm talking folks who implemented their database layer in such a way that an odd ID canonically indicated one record subtype while an even ID indicated the other. They were also quite fond of indicating that any field was null in several different ways, of indicating errors either in the field, in an object in place of the field's normal type, or sometimes in a totally separate error array - all of which could, of course, be null in different ways and none of which seemed to follow any kind of pattern.

Not saying that is what OP is dealing with here, but it serves as one example of when you might need to do a lot of weird type comparisons.

Armitag3
Mar 15, 2020

Forget it Jake, it's cybertown.


The March Hare posted:

I've been in OPs position before when writing systems to ingest absolutely insane API data.

I'm talking folks who implemented their database layer in such a way that an odd ID canonically indicated one record subtype while an even ID indicated the other. They were also quite fond of indicating that any field was null in several different ways, of indicating errors either in the field, in an object in place of the field's normal type, or sometimes in a totally separate error array - all of which could, of course, be null in different ways and none of which seemed to follow any kind of pattern.

Not saying that is what OP is dealing with here, but it serves as one example of when you might need to do a lot of weird type comparisons.

I'm getting flashbacks to ingesting SOAP requests - that of course could have been any of dozens of operations - in a single endpoint, which meant a conga line of null checks because of course every single param was optional to support any arbitrary combination. We couldn't count on anything.

QuarkJets
Sep 8, 2008

The March Hare posted:

I've been in OPs position before when writing systems to ingest absolutely insane API data.

I'm talking folks who implemented their database layer in such a way that an odd ID canonically indicated one record subtype while an even ID indicated the other. They were also quite fond of indicating that any field was null in several different ways, of indicating errors either in the field, in an object in place of the field's normal type, or sometimes in a totally separate error array - all of which could, of course, be null in different ways and none of which seemed to follow any kind of pattern.

Not saying that is what OP is dealing with here, but it serves as one example of when you might need to do a lot of weird type comparisons.

That kind of situation is what I was trying to address in my followup post; we can't control our external interfaces, but we can choose how we interact with them. If I have 10 calls to "get_x", then it's totally valid to write 10 "set to default if None" lines (one per call), but that's a pain in the rear end. And what happens if I also need to add exception handling each time that this external interface is used? That's a mess. It'd be better to write 1 function that contains None checking and replacement. This is easier to write, easier to read later, and less error prone.

If an external data structure is insanely defined then define your own, then use your sane data structure in your code.

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

QuarkJets posted:

That kind of situation is what I was trying to address in my followup post; we can't control our external interfaces, but we can choose how we interact with them. If I have 10 calls to "get_x", then it's totally valid to write 10 "set to default if None" lines (one per call), but that's a pain in the rear end. And what happens if I also need to add exception handling each time that this external interface is used? That's a mess. It'd be better to write 1 function that contains None checking and replacement. This is easier to write, easier to read later, and less error prone.

If an external data structure is insanely defined then define your own, then use your sane data structure in your code.

I'm interested in what you're getting at, but I don't fully understand. If it helps provide some context, C1, C2, and C3 rely on different external service calls.

Basically I'm generating a schedule here so I'm patching data together from several data sources, but the output from one isn't necessarily a guaranteed input to another.

For example, here's a workflow:
- Grab the current oncall for a team - this tool just gives me an email alias, no indication of what it is
- Try looking it up in LDAP. If it's not a human this step will fail, and I return None for that result.
- Recursively build a chain from the first person up their management chain.

This is just like, the C2 portion, because there's other parts before that that also have weird opportunities to fail, because my use case for these is imperfect.

For now if any of those steps fail I want the construction to fail rather than create a half-formed or empty schedule, but I also know these steps WILL fail for some services/etc because of the imperfections, so none of this should crash the program, just gracefully generate no schedule for this service.

Foxfire_
Nov 8, 2010

Falcon2001 posted:

Is there a simpler way to handle this interaction? I'm finding I'm doing this a reasonable amount.

Python code:
expected_var = object.possibly_null_attr if object.possibly_null_attr else default_value
The thing posted is a shorter equivalent, but I would not do either of them because they are actually testing for falseyness, not None like it looks like you intend:

All of these print "Default":
Python code:
class Example:
     def __init__(self):
         self.empty_list = []
         self.false_bool = False
         self.zero_int = 0
         self.none = None

example = Example()
print(example.empty_list or "Default")
print(example.false_bool or "Default")
print(example.zero_int or "Default")
print(example.none or "Default")

QuarkJets
Sep 8, 2008

Falcon2001 posted:

I'm interested in what you're getting at, but I don't fully understand. If it helps provide some context, C1, C2, and C3 rely on different external service calls.

Basically I'm generating a schedule here so I'm patching data together from several data sources, but the output from one isn't necessarily a guaranteed input to another.

For example, here's a workflow:
- Grab the current oncall for a team - this tool just gives me an email alias, no indication of what it is
- Try looking it up in LDAP. If it's not a human this step will fail, and I return None for that result.
- Recursively build a chain from the first person up their management chain.

This is just like, the C2 portion, because there's other parts before that that also have weird opportunities to fail, because my use case for these is imperfect.

For now if any of those steps fail I want the construction to fail rather than create a half-formed or empty schedule, but I also know these steps WILL fail for some services/etc because of the imperfections, so none of this should crash the program, just gracefully generate no schedule for this service.

From your earlier posts, it sounded like there was some default value for c1? If so, then write a new function. This new function uses the external service call to request c1, but if None is received then it returns the default value. Repeat for c2 and c3.

Now the rest of your code uses these sanitized functions instead of the raw external functions. You have guaranteed that c1, c2, and c3 are not None and never need to check that again. Now you can merge these objects (or whatever else) and everything will Just Work. You may still sometimes have to perform value checks, but this should reduce the number of value check lines

Mycroft Holmes
Mar 26, 2010

by Azathoth
I'm getting an unresolved reference error in the following bit of code:

code:
    def myFunc1(self, e):
        return e["year", "month", "day"]

    def listOrder1 (self, list1):
        list2 = list1
        list2.sort(key=myFunc1)
        return list2
it's saying key=myFunc1 is an unresolved reference. What am I doing wrong?

SurgicalOntologist
Jun 17, 2004

Based on "self" I'm guessing that's in a class. Functions are bound to the instance as methods, so it should be key=self.myFunc1.

Mycroft Holmes
Mar 26, 2010

by Azathoth
Having another issue. It's sayin my positional arguemnts for my functions called in main are unfilled. Specifically, list1 and temp.

main:
code:
import datetemp


class Main:
    def __init__(self):
        self.year = 0
        self.month = 0
        self.day = 0
        self.temp = 0
        self.ans = 0

    def y(self):
        print("Please input the year: ")
        year = int(input())
        while True:
            try:
                return int(year)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                year = int(input())

    def m(self):
        print("Please input the month: ")
        month = int(input())
        while True:
            try:
                return float(month)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                month = int(input())
            try:
                return 13 > month > 0
            except ValueError:
                print("Invalid input.")
                month = int(input())

    def d(self):
        print("Please input the day: ")
        day = int(input())
        while True:
            try:
                return int(day)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                day = input()
            try:
                return 32 > int(day) > 0
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                day = int(input())

    def t(self):
        print("Please input the temperature: ")
        temp = float(input())
        while True:
            try:
                return float(temp)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                temp = float(input())


def main():
    list1 = []
    a = Main()
    b = datetemp.DateTemp
    print("1 Enter a date and temperature")
    print("2 Display all date and temperature information in the order entered")
    print("3 Display all date and temperature information by date")
    print("4 Display all date and temperature information by temperature")
    print("5 Exit")
    ans = int(input())
    while ans != 5:
        if ans == 1:
            year = a.y()
            month = a.m()
            day = a.d()
            temp = a.t()
            newDict = b.createDic(year, month, day, temp)
            list1.append(newDict)
        elif ans == 2:
            print(list1)
        elif ans == 3:
            list2 = b.listOrder1(list1)
            print(list2)
        elif ans == 4:
            list3 = b.listOrder2(list1)
            print(list3)
        else:
            print("Invalid input.")
            ans = input()
    if ans == 5:
        exit()


if __name__ == "__main__":
    main()
datetemp:

code:
class DateTemp:

    def __init__(self):
        self.year = 0
        self.month = 0
        self.day = 0
        self.temp = 0
        self.list1 = []
        self.list2 = []
        self.list3 = []

    def createDic(self, year, month, day, temp):
        newDict = {
            "year": year,
            "month": month,
            "day": day,
            "temp": temp
        }
        return newDict

    def myFunc1(self, e):
        return e["year", "month", "day"]

    def myFunc2(self, e):
        return e["temp"]

    def listOrder1(self, list1):
        list2 = list1
        list2.sort(key=self.myFunc1)
        return list2

    def listOrder2(self, list1):
        list3 = list1
        list3.sort(key=self.myFunc2)
        return list3

Deffon
Mar 28, 2010

Mycroft Holmes posted:

Having another issue. It's sayin my positional arguemnts for my functions called in main are unfilled. Specifically, list1 and temp.

main:
code:
import datetemp


class Main:
    def __init__(self):
        self.year = 0
        self.month = 0
        self.day = 0
        self.temp = 0
        self.ans = 0

    def y(self):
        print("Please input the year: ")
        year = int(input())
        while True:
            try:
                return int(year)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                year = int(input())

    def m(self):
        print("Please input the month: ")
        month = int(input())
        while True:
            try:
                return float(month)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                month = int(input())
            try:
                return 13 > month > 0
            except ValueError:
                print("Invalid input.")
                month = int(input())

    def d(self):
        print("Please input the day: ")
        day = int(input())
        while True:
            try:
                return int(day)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                day = input()
            try:
                return 32 > int(day) > 0
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                day = int(input())

    def t(self):
        print("Please input the temperature: ")
        temp = float(input())
        while True:
            try:
                return float(temp)
            except ValueError:  # what float(str) raises if it fails
                print("Invalid input.")
                temp = float(input())


def main():
    list1 = []
    a = Main()
    b = datetemp.DateTemp
    print("1 Enter a date and temperature")
    print("2 Display all date and temperature information in the order entered")
    print("3 Display all date and temperature information by date")
    print("4 Display all date and temperature information by temperature")
    print("5 Exit")
    ans = int(input())
    while ans != 5:
        if ans == 1:
            year = a.y()
            month = a.m()
            day = a.d()
            temp = a.t()
            newDict = b.createDic(year, month, day, temp)
            list1.append(newDict)
        elif ans == 2:
            print(list1)
        elif ans == 3:
            list2 = b.listOrder1(list1)
            print(list2)
        elif ans == 4:
            list3 = b.listOrder2(list1)
            print(list3)
        else:
            print("Invalid input.")
            ans = input()
    if ans == 5:
        exit()


if __name__ == "__main__":
    main()
datetemp:

code:
class DateTemp:

    def __init__(self):
        self.year = 0
        self.month = 0
        self.day = 0
        self.temp = 0
        self.list1 = []
        self.list2 = []
        self.list3 = []

    def createDic(self, year, month, day, temp):
        newDict = {
            "year": year,
            "month": month,
            "day": day,
            "temp": temp
        }
        return newDict

    def myFunc1(self, e):
        return e["year", "month", "day"]

    def myFunc2(self, e):
        return e["temp"]

    def listOrder1(self, list1):
        list2 = list1
        list2.sort(key=self.myFunc1)
        return list2

    def listOrder2(self, list1):
        list3 = list1
        list3.sort(key=self.myFunc2)
        return list3

Thank you for pasting the code, the entire error message is usually useful as well for debugging.

On line 66 of main.py you assign the class datetemp.DateTemp to b.
I'm guessing that you wanted to call the constructor instead.
code:
b = datetemp.DateTemp()
The reason why Python complains is that you can call methods on the class itself, but then you don't get the implicit self argument passed for you. Your year argument was bound to the self parameter, month to year etc. and there was nothing to bind to temp.

btw, you don't seem to take advantage of using classes: you assign some variables in the constructor but then don't
read them again.
You local variables are separate from your instance variables, i.e. just because your createDic() method takes year, month, day and temp doesn't mean you need instance variables to match.
If you needed to store something away between calls you would refer to your instance variables inside a method like you do in the __init__ method with "self.whatever".

As of now, the classes act like fancy packages, like you might as well make createDic into a global function.

Mycroft Holmes
Mar 26, 2010

by Azathoth
Yeah, I’m not great at python and it’s been a few years.

QuarkJets
Sep 8, 2008

Yeah this coding style implies a Java background, where everything has to be defined as a class. It's a lot clearer if those methods, which don't use "self" at all, are just functions

I'd also recommend condensing the functions somewhat, there's a lot of extraneous stuff going on. Walk through each line of your code and make sure that it's doing what you want it to do.

Here's how I'd write your month function:

Python code:
from contextlib import suppress

def month_input():
    print("Please input the month: ")
    while True:
        month_string = input()
        with suppress(ValueError):
            month = int(month_string)
            if 0 < month < 13:
                return month
        print("Invalid input.")
Things that were changed:
1) If a non-integer gets passed in on the first input(), your function would raise a ValueError without catching it. For situations like this it's better to just accept the raw string and then do the conversion on a separate line
2) Take advantage of the loop syntax to only write one line that invokes input()
3) The try/except block is really just ignoring the exception, we can use suppress() to do that more succinctly
4) Take advantage of the fact that "return" will end the loop early if the input meets our constraints
5) Place an invalid input message at the end of the loop, to indicate that the return never occurred.
6) You should avoid single-letter names. "month_input" or "input_month" are much better names
7) Your month function had "return 13 > month > 0" as a line, which would return a bool; I don't think that's what you meant to do

The process:
1) Print something for the user to see
2) Begin infinite loop of asking for and verifying inputs
2.1) Wait for user input
2.2) Create a suppression context for ValueError
2.2.1) Attempt to convert the user input to an integer. This is where the ValueError may be raised; if it does, the remaining lines in the suppression context don't execute.
2.2.2) Check the range of the integer
2.2.3) Return the integer
2.3) A value was not returned, so something must have gone wrong. Notify the user.

Seventh Arrow
Jan 26, 2005

I made a function to determine if a string of text was a palindrome or not:

code:
def isPalindrome(s):
    text = ''.join(filter(str.isalnum, s))
    new_text = text.lower()
    if new_text[::-1] == new_text:
        print("This is a palindrome!")
    else:
        print("This is not a palindrome!")


isPalindrome("A man, a plan, a canal: Panama")
it works, but I'm not crazy about having to do
code:
new_text = text.lower()
as a separate step, though. Is there a way to add it on to the previous line? I tried
code:
new_text = ''.join(filter(str.isalnum.lower(), s))
and it wasn't having it. I think this may be largely due to the fact that "isalnum" is something I yoinked off of a search result and know little about how it actually works.

Zoracle Zed
Jul 10, 2001
short answer: "''.join(filter(str.isalnum, s)).lower()

longer answer:

filter accepts a function and an iterator. it applies the function to each element in the iterator, keeping the element if the applied function returns a truthy value. and iterating over strings iterates over their individual characters. so

Python code:
filter(str.isalnum, s)
is equivalent to:

Python code:
(char for char in s if char.isalnum())
then

Python code:
"".join(iterator_of_characters)
glues the individual characters back into a single string. which is the thing you wanted to apply lower to

Zoracle Zed fucked around with this message at 00:34 on Sep 5, 2022

Seventh Arrow
Jan 26, 2005

That makes sense, thanks!

QuarkJets
Sep 8, 2008

Seventh Arrow posted:

I made a function to determine if a string of text was a palindrome or not:

code:
def isPalindrome(s):
    text = ''.join(filter(str.isalnum, s))
    new_text = text.lower()
    if new_text[::-1] == new_text:
        print("This is a palindrome!")
    else:
        print("This is not a palindrome!")


isPalindrome("A man, a plan, a canal: Panama")
it works, but I'm not crazy about having to do
code:
new_text = text.lower()
as a separate step, though. Is there a way to add it on to the previous line? I tried
code:
new_text = ''.join(filter(str.isalnum.lower(), s))
and it wasn't having it. I think this may be largely due to the fact that "isalnum" is something I yoinked off of a search result and know little about how it actually works.

In addition to the previous post, you could try this:

code:
new_text = ''.join(filter(str.isalnum, s.lower()))

Mycroft Holmes
Mar 26, 2010

by Azathoth
okay, last part of the assignment is to have a "sort by date" function. I have set up my list entries to be dictionaries, storing year, month, and day separately. I now need to sort the list by date. my current code can only sort by one dictionary data point, i.e. by year or temp. My current code setup is:
code:
def myFunc1(self, e):
        return e["year"]

    def myFunc2(self, e):
        return e["temp"]

    def listOrder1(self, list1):
        list2 = list1
        list2.sort(key=self.myFunc1)
        return list2

    def listOrder2(self, list1):
        list3 = list1
        list3.sort(key=self.myFunc2)
        return list3
any tips on how to do this?

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
If you make your key function return a tuple instead of just a single value, then the results will be first sorted by the first element in the tuple - and if two list entries have the same first element, they will then be sorted by the second element in the returned tuple, and so on to the third element if the second elements are also equal.

Mycroft Holmes
Mar 26, 2010

by Azathoth

Jabor posted:

If you make your key function return a tuple instead of just a single value, then the results will be first sorted by the first element in the tuple - and if two list entries have the same first element, they will then be sorted by the second element in the returned tuple, and so on to the third element if the second elements are also equal.

ok, tried that, got an error.

Traceback (most recent call last):
File "C:\Users\nickt\PycharmProjects\L03\main.py", line 90, in <module>
main()
File "C:\Users\nickt\PycharmProjects\L03\main.py", line 75, in main
list2 = b.listOrder1(list1)
File "C:\Users\nickt\PycharmProjects\L03\datetemp.py", line 30, in listOrder1
list2.sort(key=self.myFunc1)
File "C:\Users\nickt\PycharmProjects\L03\datetemp.py", line 23, in myFunc1
return e[tupledate]
KeyError: ('year', 'month', 'day')

code:
def myFunc1(self, e):
        tupledate = ("year", "month", "day")
        return e[tupledate]

Deffon
Mar 28, 2010

Mycroft Holmes posted:

ok, tried that, got an error.

Traceback (most recent call last):
File "C:\Users\nickt\PycharmProjects\L03\main.py", line 90, in <module>
main()
File "C:\Users\nickt\PycharmProjects\L03\main.py", line 75, in main
list2 = b.listOrder1(list1)
File "C:\Users\nickt\PycharmProjects\L03\datetemp.py", line 30, in listOrder1
list2.sort(key=self.myFunc1)
File "C:\Users\nickt\PycharmProjects\L03\datetemp.py", line 23, in myFunc1
return e[tupledate]
KeyError: ('year', 'month', 'day')

code:
def myFunc1(self, e):
        tupledate = ("year", "month", "day")
        return e[tupledate]

Close, but instead of indexing with the tuple, you want each element of the returned tuple to be a value from the dictionary:
code:
def myFunc1(self, e):
        return (e["year"], e["month"], e["day"])
if you provided e as {"year": 1999, "month": 12, "day": 17}, then this would be returned: (1999, 12, 17).
The sorting function would take all the dictionaries, associate them with their respective tuples, sort the tuples and return the dictionaries in the same order that their associated tuples were ordered.

i.e. {"year": 1999, "month": 12, "day": 17} would be placed before {"year": 2000, "month": 1, "day": 1} because (1999, 12, 17) < (2000, 1, 1) because 1999 < 2000.

Mycroft Holmes
Mar 26, 2010

by Azathoth
new assignment, new issue. I've been provided with some code and a module called "pytest" with commands. I am to use pytest to test the code given. I have no idea how to use pytest nor is the documentation provided any good. I also do not understand portions of the provided code. My instructions are to
"Identify the appropriate testing variations for each property, method, and function. (Variations should cover boundary conditions and equivalence classes.)
Write a unit test for each variation.
Add comments to each test case briefly explaining why it should be tested.
Remember to cover validations and exceptions."

code:
# ----------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------

import datetime


# ----------------------------------------------------------------------
# DateTemp Class
# ----------------------------------------------------------------------

class DateTemp:

  def __init__(self, date, temperature):
    self._date = date
    self._temperature = temperature
  
  @property
  def date(self):
    return self._date
  
  @date.setter
  def date(self, value):
    if not isinstance(value, datetime.date):
      raise ValueError(f"Value {value} is not a datetime.date")
    self._date = value

  @property
  def temperature(self):
    return self._temperature

  @temperature.setter
  def temperature(self, value):
    # If value cannot be parsed, 'float' will raise a ValueError
    self._temperature = float(value)

  def set_date_from_ints(self, year, month, day):
    # If arguments cannot be parsed, 'int' will raise a ValueError
    # If arguments do not represent a valid date, 'datetime.date' will raise a ValueError
    self._date = datetime.date(int(year), int(month), int(day))

  def __str__(self):
    return f'The temperature on {self.date} was {self.temperature} F'

  def __repr__(self):
    return self.__str__()


# ----------------------------------------------------------------------
# Sorting Functions
# As a precondition, assume 'items' is a list of DateTemp objects
# ----------------------------------------------------------------------

def sorted_by_date(items):
  return sorted(items, key=lambda x: x.date)


def sorted_by_temp(items):
  return sorted(items, key=lambda x: x.temperature)
I do not know how to test the properties, and I barely understand the instructions.

QuarkJets
Sep 8, 2008

Mycroft Holmes posted:

new assignment, new issue. I've been provided with some code and a module called "pytest" with commands. I am to use pytest to test the code given. I have no idea how to use pytest nor is the documentation provided any good. I also do not understand portions of the provided code. My instructions are to
"Identify the appropriate testing variations for each property, method, and function. (Variations should cover boundary conditions and equivalence classes.)
Write a unit test for each variation.
Add comments to each test case briefly explaining why it should be tested.
Remember to cover validations and exceptions."

code:
# ----------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------

import datetime


# ----------------------------------------------------------------------
# DateTemp Class
# ----------------------------------------------------------------------

class DateTemp:

  def __init__(self, date, temperature):
    self._date = date
    self._temperature = temperature
  
  @property
  def date(self):
    return self._date
  
  @date.setter
  def date(self, value):
    if not isinstance(value, datetime.date):
      raise ValueError(f"Value {value} is not a datetime.date")
    self._date = value

  @property
  def temperature(self):
    return self._temperature

  @temperature.setter
  def temperature(self, value):
    # If value cannot be parsed, 'float' will raise a ValueError
    self._temperature = float(value)

  def set_date_from_ints(self, year, month, day):
    # If arguments cannot be parsed, 'int' will raise a ValueError
    # If arguments do not represent a valid date, 'datetime.date' will raise a ValueError
    self._date = datetime.date(int(year), int(month), int(day))

  def __str__(self):
    return f'The temperature on {self.date} was {self.temperature} F'

  def __repr__(self):
    return self.__str__()


# ----------------------------------------------------------------------
# Sorting Functions
# As a precondition, assume 'items' is a list of DateTemp objects
# ----------------------------------------------------------------------

def sorted_by_date(items):
  return sorted(items, key=lambda x: x.date)


def sorted_by_temp(items):
  return sorted(items, key=lambda x: x.temperature)
I do not know how to test the properties, and I barely understand the instructions.

First thing you need to do is the same thing that you need to do any time that you encounter a new thing: google it. Go read some pytest documentation. Most people packages have a beginner-friendly intro page and some more in-depth examples to give you a sense for how to use it. If you have any questions you should google them first, because odds are good that there's some stackoverflow answer with 1000 up-votes already. Then ask them here if you still have questions.

pytest is basically a package that builds on top an older python library (unittest) for unit testing. It makes unit testing fairly easy, all things considered, and it's backwards compatible to make switching over easy.

Your instructions could be read as "write a bunch of good unit tests that also provide 100% coverage for this code sample." Coverage is what it sounds like: if a line of code was executed during testing, then it's covered. For instance, the date setter of the class you were provided can raise a ValueError, so in order to have full coverage of that method you'd need to 1) provide an input that does not raise the exception and 2) provide another input that does.

The instructions are also saying to write sensible tests that test basic functionality while also poking at boundary conditions. You could, for instance, verify that various strings like "42.50" successfully convert to floats when passed to the temperature setter, and then you could check bad inputs (such as the string "bad input") result in a ValueError. You could check that set_date_from_ints() raises a ValueError if given a month of less than 1 or greater than 12, things like that. You also have this sorted_by_temp() function; test_sorted_by_temp() should create a list of unsorted DateTemp items, pass that list to sorted_by_temp(), and then asserts that the returned values are sorted by temperature. The comments in the code are hinting at many of the edge cases you should definitely consider.

QuarkJets fucked around with this message at 05:33 on Sep 9, 2022

Mycroft Holmes
Mar 26, 2010

by Azathoth
ok, I am attempting to test the property "date" but my code is giving an error saying they can't find the function and I don't even know if my test is correct. Assume code from my previous question is what I am attempting to import and reference.

code:
import datetemp

a = datetemp

def date_property_test():
    assert a.date(3) == 3

Mycroft Holmes
Mar 26, 2010

by Azathoth
ok, it's finally finding it, but saying properties are not callable. Then why did my teacher say to test properties? Ugh. It's also refusing to let me test the setter, which has the same name.

code:
def test_date1():
    x = datetime.datetime(2010, 1, 1)
    assert datetemp.DateTemp.date(x) == x


def test_date2():
    x = 10
    with pytest.raises(ValueError):
        datetemp.DateTemp.date(x)

Deffon
Mar 28, 2010

Mycroft Holmes posted:

ok, it's finally finding it, but saying properties are not callable. Then why did my teacher say to test properties? Ugh. It's also refusing to let me test the setter, which has the same name.

code:
def test_date1():
    x = datetime.datetime(2010, 1, 1)
    assert datetemp.DateTemp.date(x) == x


def test_date2():
    x = 10
    with pytest.raises(ValueError):
        datetemp.DateTemp.date(x)

Properties are not callable like methods, because the point of properties is that you read/write their values like they were instance variables. However, unlike with instance variables, you have a chance to change what happens when read and write properties. In this case there is an check that you always write a date. Also remember that you need an instance of your class to call methods and properties on.
code:
dt = datetemp.DateTemp()
dt.date = whatever
print(dt.date)

Mycroft Holmes
Mar 26, 2010

by Azathoth
ok, had to kludge some stuff because it wouldn't let me have a datetemp call without passing information. my new error is "TypeError: 'datetime.datetime' object is not callable"

code:
import pytest

import datetemp
import datetime

dt = datetemp.DateTemp(datetime.datetime(2010, 1, 1), 100)


def test_date1():
    x = datetime.datetime(2010, 1, 1)
    assert dt.date(x) == x

QuarkJets
Sep 8, 2008

Uhh okay guess I won't bother responding to permabanned pedo poster Mycroft Holmes

ihatemyself
Sep 9, 2022

by Pragmatica
well, poo poo. now how am I going to get help with my homework?

(USER WAS PERMABANNED FOR THIS POST)

Macichne Leainig
Jul 26, 2012

by VG

ihatemyself posted:

well, poo poo. now how am I going to get help with my homework?

Just remember list comprehensions exist and you'll be fine.

ihatemyself
Sep 9, 2022

by Pragmatica
I'm still having the above issue, btw.

QuarkJets
Sep 8, 2008

ok

Jose Cuervo
Aug 25, 2004
Not saying this is the optimal way to do this, but I have a file params.py which looks like this:

Python code:
#params.py
top_threshold = None
lag = None
delta = None
In short, it contains the various parameters for an algorithm (the actual list is about 15 parameters). Now I would like to make several runs of the algorithm using different parameter values. Using a Juptyer notebook, I have imported the module in the first cell and then in the second cell I assign the new parameter values as follows:


Python code:
import params

params.top_threshold = 15
params.lag = 2
params.delta = 3.9
However, when I run the actual algorithm by calling it in subsequent cells, an error is thrown in the code whenever one of the parameters is used because the parameter is still None. Why?

boofhead
Feb 18, 2021

Uh.. I'm not actually sure. I mean, I'd do it differently, but I'm not sure why what you have there isn't working for you. I was curious so I tested it in plain python and it works, but I don't have Jupyter Notebook installed so can't double check there. You sure you're not accidentally re-importing params.py between runs, but missing executing the cell where you redefine it?

Sorry, I've used Jupyter Notebooks for all of a day in my life

CarForumPoster
Jun 26, 2013

⚡POWER⚡
It sounds like you’re reimporting them. Is your notebook set to reimport on cell execution or something?

SurgicalOntologist
Jun 17, 2004

I tested in a notebook and couldn't reproduce. Even reimporting doesn't reset the value.

How are you referencing params in the algorithm? Is it in the notebook or imported? Could be something there.

Adbot
ADBOT LOVES YOU

QuarkJets
Sep 8, 2008

Jose Cuervo posted:

Not saying this is the optimal way to do this, but I have a file params.py which looks like this:

Python code:
#params.py
top_threshold = None
lag = None
delta = None
In short, it contains the various parameters for an algorithm (the actual list is about 15 parameters). Now I would like to make several runs of the algorithm using different parameter values. Using a Juptyer notebook, I have imported the module in the first cell and then in the second cell I assign the new parameter values as follows:


Python code:
import params

params.top_threshold = 15
params.lag = 2
params.delta = 3.9
However, when I run the actual algorithm by calling it in subsequent cells, an error is thrown in the code whenever one of the parameters is used because the parameter is still None. Why?

I don't know the right words to use to describe this, but if you're trying to update a module such that the values being imported change between iterations then this won't work and it would be bad practice even if it did. Instead, define a dataclass in params.py, then create an instance of it in your notebook, and update that instance's values as needed and just pass it around to wherever it needs to go

QuarkJets fucked around with this message at 22:14 on Sep 13, 2022

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