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
Warbird
May 23, 2012

America's Favorite Dumbass

Hey folks. I've been working on a small python project for a bit now and I'm happy enough with the implementation that I'd like to try my hand at packaging it up and throwing it onto PyPi so people can try it out.

I'm about 95% of the way there, but I have two outstanding questions that I can't find a way to address due to the search terms being too generic (and a general lack of understanding on my part).

1. Right now the package works exactly as I'd like except that you have to call it with 'python3 /path/to/package'. Normally this would be address via a python shebang, and I've included it in the script actual, but that doesn't seem to work once the script is inside a package. My next guess would have been __init__.py or __main__.py, but it doesn't seem to be that either.

2. Is there any sort of inbuilt functionality to have the package add itself to the system path or have the package install itself to a location within the path? I apologize if this it overly basic, anything I can try to google on the matter is from an end user perspective and doesn't have too much play in this area.


Edit: It looks like both of these were addressed by the info on this page: https://setuptools.pypa.io/en/latest/userguide/entry_point.html?highlight=entry-point#console-scripts though I'll readily admit I do not understand it. Whoulda thunk a youtube video might leave out important details? :v: I suspect am highly aware that the entire package structure is a drat mess, but a working implementation comes first. Refinement will come in time.

Edit2: Ok, after some futzing around it looks like the root of the issue was that I needed to set up a main() function and then call that into the whatsits and that appears to have sorted things out nicely. If anyone would be interested in trying out the module I'd be interested in hearing feedback: https://test.pypi.org/project/streamers/

If anyone is interested, the code repo is here and I'd really appreciate any feedback that could be given. I've not done programming proper in a long time and even then that wasn't in the world of Python.

Warbird fucked around with this message at 00:23 on Jul 16, 2022

Adbot
ADBOT LOVES YOU

Warbird
May 23, 2012

America's Favorite Dumbass

Oh my god.

I've been fighting with pip/setuptools for hours trying to find out why the hell the requests library dependency wasn't being found and pulled down. As it turns out, requests doesn't have a presence on the test PyPi setup and since my package was hosted there pip was checking it instead of the real deal and erroring out. While I can see that as things working as designed it really should be documented somewhere, but I'm not sure if that's a setuptools bug or something to do with pip itself.

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

QuarkJets posted:

I don't know of any decent class tutorials because every single one that I've ever seen deep throats the OOP cancer :shrug:

You gotta be more specific about wtf this is, because classes are deeply tied to OOP.

Edit: Missed that you were replying to someone, thought you were asking for a tutorial. My bad.

Falcon2001 fucked around with this message at 01:16 on Jul 17, 2022

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

Seventh Arrow posted:

Lo and behold, the same printout with half the calories! Or something. Would there ever be a reason to use .format() or a string concat instead of f-strings?

Also, I feel like Codecademy kind of rushed this complex subject (classes, not f-strings) and I feel like I've been smacked with a frying pan. Can someone point me to a decent tutorial on classes?

Two things here - for starters, the biggest reason I've seen to use .format() is if you want to save a template and use it somewhere other than where it's being interpreted. For example, in a piece of software I maintain, we have some basic templates for the small amount of human readable outputs we do (emails/tickets/etc). These are stored in text files and have placeholders for {email_address} etc throughout.

But for most smaller stuff, f-strings are great and you should stick to them.

Second off, the whole classes thing. I really liked Automate The Boring Stuff as my intro to programming, so here's his chapter from another book on classes - I haven't read it, but in general these books are well recommended, so try it out: http://inventwithpython.com/beyond/chapter15.html

IMO: Classes are basically the blueprints for objects that form the backbone of Object Oriented Programming. A class defines details about the object, such as attributes (what is this object?) and methods (what can this object do?) - so for example, you have the classic animal OOP example:

code:

class Cat:

height: int
weight: int
color: str
name: str

def __init__(self, height: int, weight: int, color: str, name: str):
    self.height = height
    self.weight = weight
    self.color = color
    self.name = name

def purr(self):
   print(f"{self.name} purrs loudly.")
And then in practice:
code:
new_animal = cat(100,100,'red','doofus')
new_animal.purr()
>>> "doofus purrs loudly"
This is obviously a ridiculously small example, but basically everything in python is an object defined by a class. Lists, for example, are simply objects defined by the standard library to behave in certain ways.

QuarkJets above referred to the "OOP cancer", which, without speaking for them too closely, generally refers to how much of a tangled spaghetti mess poorly written OOP can be when you start getting into inheritance/etc. In general though, if you're writing Python, you need to understand OOP principles because Python is inherently OOP by design, and the majority of software written in it adheres to OOP practices. There's lots of footguns in OOP, mostly around things like maintainability of code, but there's also extremely robust discussion around avoiding those footguns and writing better code outside of it, all of which is probably past your current skill / knowledge level, so I wouldn't worry about it too much.

Seventh Arrow
Jan 26, 2005

Thanks! I will take a look at Al Sweigart's chapter - although later on in the Codecademy course they had a project that helped me get a better idea of how classes are used and what they're good for:

https://gist.github.com/jalcoding8/fcac42c43d378c6943274ece219bde0f

I could see how the Business could contain the Franchise and the Menu, and the Franchise could contain the Menu, and so on; and how they could also have functions that interact. I'm curious though - the order of stuff doesn't seem to matter for the most part. Like if the "Menu" class was before "Franchise", would the "calculate_bill" function (line 43) still work?

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

Seventh Arrow posted:

Thanks! I will take a look at Al Sweigart's chapter - although later on in the Codecademy course they had a project that helped me get a better idea of how classes are used and what they're good for:

https://gist.github.com/jalcoding8/fcac42c43d378c6943274ece219bde0f

I could see how the Business could contain the Franchise and the Menu, and the Franchise could contain the Menu, and so on; and how they could also have functions that interact. I'm curious though - the order of stuff doesn't seem to matter for the most part. Like if the "Menu" class was before "Franchise", would the "calculate_bill" function (line 43) still work?

The order doesn't really matter (There's a BUT there around things like type hinting but it's pretty minimal) because you define your classes before you start actually executing code. Everything inside a class is a blueprint, so it's executed when you create a new object from that class, but the stuff starting on line 54 is actually code that's being executed. So if you tried executing that code before you defined the class (like say, move it to line 2) it will fail.

Edit: a side interesting note: every time you import a module, it basically goes through and interprets all the code in there, which is why you see the weird pattern sometimes of if __name__ == "__main__" pattern in modules/scripts sometimes, because that basically lets you write code that won't be executed on import, only if you're running the file directly.

Falcon2001 fucked around with this message at 20:01 on Jul 17, 2022

Seventh Arrow
Jan 26, 2005

I ran into a challenge where I think my solution was more efficient than the official solution, but I have this feeling that I'm overlooking something.

The challenge was to use a function that takes a text string and returns a new string with every other letter. The official solution was as follows:

code:
def every_other_letter(word):
  every_other = ""
  for i in range(0, len(word), 2):
    every_other += word[i]
  return every_other
print(every_other_letter("Codecademy"))

results in 'Cdcdm'

This was my solution:

code:
def every_other_letter(word):
  new_string = " "
  return new_string + word[::2]
print(every_other_letter("Codecademy"))

results in 'Cdcdm'

Is there something I'm overlooking, or is it really that simple?

fisting by many
Dec 25, 2009



No, that works fine, maybe they just wanted to teach for loops and range rather than slices at that point.

Jigsaw
Aug 14, 2008

Seventh Arrow posted:

I ran into a challenge where I think my solution was more efficient than the official solution, but I have this feeling that I'm overlooking something.

The challenge was to use a function that takes a text string and returns a new string with every other letter. The official solution was as follows:

code:
def every_other_letter(word):
  every_other = ""
  for i in range(0, len(word), 2):
    every_other += word[i]
  return every_other
print(every_other_letter("Codecademy"))

results in 'Cdcdm'

This was my solution:

code:
def every_other_letter(word):
  new_string = " "
  return new_string + word[::2]
print(every_other_letter("Codecademy"))

results in 'Cdcdm'

Is there something I'm overlooking, or is it really that simple?

You could even just do
code:
def every_other_letter(word):
    return word[::2]
you don’t need new_string using your approach. (If you’re concerned about modifying the string in place, remember that strings are immutable so that’s not a concern anyway.)

Not sure which one works out to be more efficient, but you could figure it out easily enough using timeit.

Bad Munki
Nov 4, 2008

We're all mad here.


In fact, the use of new_string = " " and then concatenating on to it results in " Cdcdm" with a leading space. That's easily fixed but yeah, just return that directly. Or if you super duper wanna force it into a string,
code:
return f'{word[::2]}'
# or
return str(word[::2])
But the print() does that anyhow.

Seventh Arrow
Jan 26, 2005

The next exercise is similar, returning a string in reverse order.

Their solution:

code:
def reverse_string(word):
  reverse = ""
  for i in range(len(word)-1, -1, -1):
    reverse += word[i]
  return reverse
print(reverse_string("Codecademy"))

gets the result ymedacedoC

My (new, goonified) solution:

code:
def reverse_string(word):
  return(word[::-1])
print(reverse_string("Codecademy"))

gets the result ymedacedoC

The challenge doesn't mention anything about teaching loops or range. Guess they want to show another way of doing things!

QuarkJets
Sep 8, 2008

Their solution is dumb and bad, holy smokes. If they absolutely had to use a for loop the least they could do is iterate over the characters instead of using indexing

Python code:
def reverse_string(word):
  reverse = ""
  for c in reversed(word):
    reverse += c
  return reverse
Your solution with slicing is still better than this though

boofhead
Feb 18, 2021

fisting by many posted:

No, that works fine, maybe they just wanted to teach for loops and range rather than slices at that point.

This is absolutely the point, and worth considering. They have a syllabus and these contrived examples have been designed to help teach you concepts for that syllabus, they're not supposed to be the "objective best all-context solution" for whatever the issue is - if the premise of the issue even makes any sense at all. It's just an educational tool to get you practising whatever the week's topic is

e: I'm reminded of taking beginner (human) language classes and there was some guy I got paired with on some little "using the vocabulary provided and the grammar you know, have a conversation about what you did this weekend" task, and he refused to do it because none of the vocabulary covered what he did this weekend. A modern day George Washington

boofhead fucked around with this message at 09:24 on Jul 18, 2022

ExcessBLarg!
Sep 1, 2001

boofhead posted:

They have a syllabus and these contrived examples have been designed to help teach you concepts for that syllabus, they're not supposed to be the "objective best all-context solution" for whatever the issue is - if the premise of the issue even makes any sense at all. It's just an educational tool to get you practising whatever the week's topic is
While true, I'd punch someone if they wrote a string reverse the "official" way. -1 -1 -1 -1 -1 -1

boofhead
Feb 18, 2021

Fair

Personally in a professional environment I'd just give them one of the old

Python code:
def reverse_string(value):
    import string
    deconstructed_list_comprehension_pool = []
    for i in range(0, len(value)):
        all_chars = list(string.printable)
        i_new = len(value) - i - 1
        for n in range(0, len(all_chars)):
            if all_chars[n] == value[i_new]:
                deconstructed_list_comprehension_pool.append(all_chars[n])
    reconstructed_deconstructed_list_comprehension_pool_final = [c for c in deconstructed_list_comprehension_pool]
    return ''.join(reconstructed_deconstructed_list_comprehension_pool_final)
so that boss knows im working

Seventh Arrow
Jan 26, 2005

This is useful in those situations where you get paid by the line!

Seventh Arrow
Jan 26, 2005

So what I came up with on this challenge is pretty close to what their solution was.

The problem is that I'm not sure how or why it works.



Here's the code:

code:
def sum_even_keys(my_dictionary):
  total = 0
  for k in my_dictionary.keys():
    if k % 2 == 0:
      total += my_dictionary[k]
  return total
print(sum_even_keys({10:1, 100:2, 1000:3})) gets you 6, since of course 10, 100, and 1000 are all even numbers, then 1 + 2 + 3 = 6

But I'm not getting why it's returning the values instead of the keys. Shouldn't total += my_dictionary[k] be summing the keys instead of the corresponding value?

And how would you do it if you did need to add up the keys instead of the values?

fisting by many
Dec 25, 2009



Seventh Arrow posted:

So what I came up with on this challenge is pretty close to what their solution was.

The problem is that I'm not sure how or why it works.



Here's the code:

code:
def sum_even_keys(my_dictionary):
  total = 0
  for k in my_dictionary.keys():
    if k % 2 == 0:
      total += my_dictionary[k]
  return total
print(sum_even_keys({10:1, 100:2, 1000:3})) gets you 6, since of course 10, 100, and 1000 are all even numbers, then 1 + 2 + 3 = 6

But I'm not getting why it's returning the values instead of the keys. Shouldn't total += my_dictionary[k] be summing the keys instead of the corresponding value?

And how would you do it if you did need to add up the keys instead of the values?

k is the key, my_dictionary[k] is the value.

Sad Panda
Sep 22, 2004

I'm a Sad Panda.
In lists, you retrieve the value of an item in it by using its index.

Python code:

my_list[i] 
Gets the ith value from my_list .

With dictionaries,
code:

my_dictionary[k] 
Returns the value associated with the key k.

QuarkJets
Sep 8, 2008

Seventh Arrow posted:

So what I came up with on this challenge is pretty close to what their solution was.

The problem is that I'm not sure how or why it works.



Here's the code:

code:
def sum_even_keys(my_dictionary):
  total = 0
  for k in my_dictionary.keys():
    if k % 2 == 0:
      total += my_dictionary[k]
  return total
print(sum_even_keys({10:1, 100:2, 1000:3})) gets you 6, since of course 10, 100, and 1000 are all even numbers, then 1 + 2 + 3 = 6

But I'm not getting why it's returning the values instead of the keys. Shouldn't total += my_dictionary[k] be summing the keys instead of the corresponding value?

And how would you do it if you did need to add up the keys instead of the values?

k is the key, my_dictionary[k] is the value. So if you did need to add up the keys you could just sum over each k instead of my_dictionary[k]

code:
def sum_even_keys(my_dictionary):
  total = 0
  for k in my_dictionary.keys():
    if k % 2 == 0:
      total += k
  return total
For summing the values of even keys, here's how I would do it:

code:
def sum_even_keys(my_dictionary):
    return sum(v for k, v in my_dictionary.items() if k % 2 == 0)
dict.items() returns a view of key-value pairs in the dictionary. This is used in a generator comprehension to yield all values for keys that are even. This generator is provided to sum()

QuarkJets fucked around with this message at 08:12 on Jul 20, 2022

Seventh Arrow
Jan 26, 2005

Thanks for the explanations!

Jigsaw
Aug 14, 2008

Seventh Arrow posted:

So what I came up with on this challenge is pretty close to what their solution was.

The problem is that I'm not sure how or why it works.



Here's the code:

code:
def sum_even_keys(my_dictionary):
  total = 0
  for k in my_dictionary.keys():
    if k % 2 == 0:
      total += my_dictionary[k]
  return total
print(sum_even_keys({10:1, 100:2, 1000:3})) gets you 6, since of course 10, 100, and 1000 are all even numbers, then 1 + 2 + 3 = 6

But I'm not getting why it's returning the values instead of the keys. Shouldn't total += my_dictionary[k] be summing the keys instead of the corresponding value?

And how would you do it if you did need to add up the keys instead of the values?

FYI, you don’t need .keys() here. Iterating over a dict iterates over the keys by default. So you could just use for k in my_dictionary and leave the rest the same. It’s equivalent.

But the better way to do it in this case is the sum() + generator comprehension above anyway.

Jigsaw fucked around with this message at 12:38 on Jul 20, 2022

CarForumPoster
Jun 26, 2013

⚡POWER⚡

Jigsaw posted:

FYI, you don’t need .keys() here. Iterating over a dict iterates over the keys by default. So you could just use for k in my_dictionary and leave the rest the same. It’s equivalent.

But the better way to do it in this case is the sum() + generator comprehension above anyway.

boo ambiguity, boo

ExcessBLarg!
Sep 1, 2001

CarForumPoster posted:

boo ambiguity, boo
While I think there's good argument that items() should have been the default iterable view of a dictionary, I wouldn't say this behavior is particularly ambiguous.

CarForumPoster
Jun 26, 2013

⚡POWER⚡

ExcessBLarg! posted:

While I think there's good argument that items() should have been the default iterable view of a dictionary, I wouldn't say this behavior is particularly ambiguous.

Depends on how well controlled the input is. If you're sure its always getting a dict, then you'll always get the same behavior.

If some bastard coated bastard ships you a string or a list you might now have anomalous behavior rather than an AttributeError

Seventh Arrow
Jan 26, 2005

Well I managed to solve something by my very own self with just a search engine and bucket full of tears.

The latest challenge was thus:



My original code was thus:

code:
def word_length_dictionary(words):
  new_dict = {}
  for word in words:
    w_length = len(word)
    new_dict = {key:value for key, value in zip(word, w_length)}
  return new_dict
and so then running the following: print(word_length_dictionary(["apple", "dog", "cat"]))

earned me this precious nugget:

quote:

TypeError: 'int' object is not iterable

I'll spare you the parts with the tears and the alcohol consumption and the long walk under a full moon but anyways I got the bright idea to see just what happens when you run the following in a separate jupyter window:

code:
for word in words:
    w_length = len(word)
So when you print out w_length, you just get discrete integers, which you of course can't iterate over. I realized that I had to slap it into a list or dictionary or whatever. I could even use a LIST COMPREHENSION, hell yeah.

So after a few mutations, I wound up with this:

code:
def word_length_dictionary(words):
  for word in words:
    w_length = [len(word) for word in words]
    new_dict = {key:value for key, value in zip(words, w_length)}
  return new_dict
Now granted, their official solution seems more elegant:

code:
def word_length_dictionary(words):
  word_lengths = {}
  for word in words:
    word_lengths[word] = len(word)
  return word_lengths
But I'll take what I can get! Now give me that high-paying data engineer job, sir or madam!

Seventh Arrow fucked around with this message at 04:16 on Jul 22, 2022

Jigsaw
Aug 14, 2008

Seventh Arrow posted:

Well I managed to solve something by my very own self with just a search engine and bucket full of tears.

The latest challenge was thus:



My original code was thus:

code:
def word_length_dictionary(words):
  new_dict = {}
  for word in words:
    w_length = len(word)
    new_dict = {key:value for key, value in zip(word, w_length)}
  return new_dict
and so then running the following: print(word_length_dictionary(["apple", "dog", "cat"]))

earned me this precious nugget:

I'll spare you the parts with the tears and the alcohol consumption and the long walk under a full moon but anyways I got the bright idea to see just what happens when you run the following in a separate jupyter window:

code:
for word in words:
    w_length = len(word)
So when you print out w_length, you just get discrete integers, which you of course can't iterate over. I realized that I had to slap it into a list or dictionary or whatever. I could even use a LIST COMPREHENSION, hell yeah.

So after a few mutations, I wound up with this:

code:
def word_length_dictionary(words):
  for word in words:
    w_length = [len(word) for word in words]
    new_dict = {key:value for key, value in zip(words, w_length)}
  return new_dict
Now granted, their official solution seems more elegant:

code:
def word_length_dictionary(words):
  word_lengths = {}
  for word in words:
    word_lengths[word] = len(word)
  return word_lengths
But I'll take what I can get! Now give me that high-paying data engineer job, sir or madam!

I’m really not sure what their fetish for explicit “for” loops over comprehensions is. I get the sense that the course wasn’t really developed with Pythonic code in mind. Here’s a solution that uses a dict comprehension to do it all in one go:
code:
def word_length_dictionary(words):
    return {word: len(word) for word in words}
Or using a lambda to make it a one-liner:
code:
word_length_dictionary = lambda words: {word: len(word) for word in words}
That said, the more important thing you’ve gotten practice with is figuring out how to come up with a working solution to a problem on your own—that’s a great skill to have, so nicely done! It may not have been the best solution (the post below says why), but that will come more easily with time and experience.

Jigsaw fucked around with this message at 05:42 on Jul 22, 2022

QuarkJets
Sep 8, 2008

Seventh Arrow posted:

Well I managed to solve something by my very own self with just a search engine and bucket full of tears.

The latest challenge was thus:



My original code was thus:

code:
def word_length_dictionary(words):
  new_dict = {}
  for word in words:
    w_length = len(word)
    new_dict = {key:value for key, value in zip(word, w_length)}
  return new_dict
and so then running the following: print(word_length_dictionary(["apple", "dog", "cat"]))

earned me this precious nugget:

I'll spare you the parts with the tears and the alcohol consumption and the long walk under a full moon but anyways I got the bright idea to see just what happens when you run the following in a separate jupyter window:

code:
for word in words:
    w_length = len(word)
So when you print out w_length, you just get discrete integers, which you of course can't iterate over. I realized that I had to slap it into a list or dictionary or whatever. I could even use a LIST COMPREHENSION, hell yeah.

So after a few mutations, I wound up with this:

code:
def word_length_dictionary(words):
  for word in words:
    w_length = [len(word) for word in words]
    new_dict = {key:value for key, value in zip(words, w_length)}
  return new_dict
Now granted, their official solution seems more elegant:

code:
def word_length_dictionary(words):
  word_lengths = {}
  for word in words:
    word_lengths[word] = len(word)
  return word_lengths
But I'll take what I can get! Now give me that high-paying data engineer job, sir or madam!

code:
def word_length_dictionary(words):
  for word in words:
    w_length = [len(word) for word in words]
    new_dict = {key:value for key, value in zip(words, w_length)}
  return new_dict
Let's break this down:

1. For each word in the list of words
2. Create a list containing the lengths of all of the words
3. Create dict containings all of the words and word lengths
4. End loop over words
5. Return a dictionary.

A list comprehension and a dict comprehension are kinds of loops, so you have nested loops. In the first iteration of the outer loop you complete the work of finding all of the word lengths and creating a dictionary mapping all of the words to their lengths. In each subsequent iteration, you're re-computing the same results and re-assigning them to the old variables, effectively throwing away the old results. You only need the first iteration of the loop, e.g. you don't need the outer loop at all. Here's how I'd do it:

code:
def word_length_dictionary(words):
    """You should always include a docstring when you define a function."""
    return {word: len(word) for word in words}
The official solution is fine and to-the-letter: their function does exactly what the prompt asks.

pmchem
Jan 22, 2010


I’d like to traverse a set of Windows (sub)directories (including all subdirs) containing tens of thousands of files and find all duplicate files, including those in different directories, and including those with “- Copy” or “(1)” suffixes. The report the absolute path of dupes so I can decide which to delete later.

Anyone have an idea for an efficient way to do this? I could also use powershell but this seems more like a short python script.

pmchem fucked around with this message at 14:15 on Jul 22, 2022

Apex Rogers
Jun 12, 2006

disturbingly functional

You could probably do this with os.walk(). You would need to do something to canonicalize the file name, e.g. handle the “- Copy” and “(1)” suffixes, but once you have that you can keep a dictionary of file names, checking if the key exists each time (if so, it’s a duplicate). Duplicates can go into a separate list or whatever that you dump at the end. Obviously, you’ll want to keep the actual file name around to go into this list, rather than the canonicalized version.

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

Apex Rogers posted:

You could probably do this with os.walk(). You would need to do something to canonicalize the file name, e.g. handle the “- Copy” and “(1)” suffixes, but once you have that you can keep a dictionary of file names, checking if the key exists each time (if so, it’s a duplicate). Duplicates can go into a separate list or whatever that you dump at the end. Obviously, you’ll want to keep the actual file name around to go into this list, rather than the canonicalized version.

Keep a dict[str, list[Path]] where the key is the canonicalized name, and the value is a list of all file paths associated with it.

Iterate over the whole directory with os.walk() and then every file just figure out what it's canonical name is, then toss it in the dictionary. One final iteration over the dict to filter out any entry with a value length of 1 or less, and boom, there you go.

a foolish pianist
May 6, 2007

(bi)cyclic mutation

Jigsaw posted:

I’m really not sure what their fetish for explicit “for” loops over comprehensions is. I get the sense that the course wasn’t really developed with Pythonic code in mind. Here’s a solution that uses a dict comprehension to do it all in one go:

Seems like it’s an introduction to programming that uses python, not an introduction to python. I’d figure that for beginners, using features that are available in lots of languages is better than using very pythonic solutions.

Jigsaw
Aug 14, 2008

a foolish pianist posted:

Seems like it’s an introduction to programming that uses python, not an introduction to python. I’d figure that for beginners, using features that are available in lots of languages is better than using very pythonic solutions.

Ah, that’d make a lot of sense.

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug
Speaking of general design pattern problems: I have some questions about Dependency Injection.

I'm reworking some of my code, and one area in particular has a lot of dependencies because it deals with a large number of various internal services. This is all functions, no classes in this module, for context.

My initial setup is setting up a dataclass that just holds those dependencies, initializing it at the entrypoint to this portion of the codebase with a factory function, and then passing that down as the calls flow through. This allow for dependency injection as I have a factory function that builds it the 'default' way, but I can also easily pass in appropriately configured mocks / test replacements for testing.

Is there any significant disadvantage to doing this?

QuarkJets
Sep 8, 2008

The trouble that I usually see is people use dependency injection to just move an obscure environment variable from one place to another, passing it in with literally the same variable name without thinking about what their code actually does. On top of the more well-known advantages, another big advantage of using dependency injection is that you can write code that is easier to read and self-documenting. If you're refactoring anyway then you may as well write the best possible signatures

I think that the biggest pitfall here is that you are probably making changes that aren't backwards compatible, so you have to be carefully looking for broken code and controlling your version numbers and package dependencies.

The specific approach you're using is good and is the very common "put the arguments in a struct" mode of simplifying overly long function signatures. Be careful about adding too many parameters that aren't actually common to multiple services, e.g. your functions should only receive parameters that are relevant to them; otherwise this can create confusion later. Ask yourself, which of the parameters in this dataclass is actually used by this function?" The answer should be "all of them"

QuarkJets fucked around with this message at 21:58 on Jul 22, 2022

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

QuarkJets posted:

The trouble that I usually see is people use dependency injection to just move an obscure environment variable from one place to another, passing it in with literally the same variable name without thinking about what their code actually does. On top of the more well-known advantages, another big advantage of using dependency injection is that you can write code that is easier to read and self-documenting. If you're refactoring anyway then you may as well write the best possible signatures

I think that the biggest pitfall here is that you are probably making changes that aren't backwards compatible, so you have to be carefully looking for broken code and controlling your version numbers and package dependencies.

The specific approach you're using is good and is the very common "put the arguments in a struct" mode of simplifying overly long function signatures. Be careful about adding too many parameters that aren't actually common to multiple services, e.g. your functions should only receive parameters that are relevant to them; otherwise this can create confusion later. Ask yourself, which of the parameters in this dataclass is actually used by this function?" The answer should be "all of them"

Thanks! In this case it's a reasonably shallow section of code that just happens to have a lot of weird api calls to make. Unfortunately, this also means that the dependency chain is...more complicated than most. For example, Function X calls API 1 for all services, then I filter based on a call to API 2, and then pass to a subfunction that does API 1 and 3, but not two. But it in turn has a helper function that does call to API 2.

This all probably means I didn't do a good enough job writing this up in the first place, but it does mean that while a function might not need a specific dependency, a helper function it relies on does. Luckily this madness is confined to this particular module and won't pollute the rest of the codebase.

Seventh Arrow
Jan 26, 2005

QuarkJets posted:


A list comprehension and a dict comprehension are kinds of loops, so you have nested loops. In the first iteration of the outer loop you complete the work of finding all of the word lengths and creating a dictionary mapping all of the words to their lengths. In each subsequent iteration, you're re-computing the same results and re-assigning them to the old variables, effectively throwing away the old results. You only need the first iteration of the loop, e.g. you don't need the outer loop at all. Here's how I'd do it:

code:
def word_length_dictionary(words):
    """You should always include a docstring when you define a function."""
    return {word: len(word) for word in words}
The official solution is fine and to-the-letter: their function does exactly what the prompt asks.

Yeah at this point I feel like I'm just taking blocks of code and mashing them together based on what's in my continually-updating cheat sheet. I want to get a deeper understanding of how these things work but I think it's just part of the process and I need to be patient. I think it's sort of like how at the start every musician sounds like their favorite musician until they start expanding their musical vocabulary. I will probably get a tutor from fiverr later but I want to kind of struggle through the course first and see how much I can figure out on my own.

QuarkJets
Sep 8, 2008

A good exercise is to open up a terminal and copy in code, run it, and start printing variables to see what's going on. Less is more here, inspect the results of each line if you aren't sure what's going on. A little more advanced than that would be using a debugger or a jupyter notebook. Basically execute one line and then check to see see what happened to each variable in the line. Pretty quickly you'll figure out how things work

"run some simple set of lines and see what it does" is a valuable habit that I still use

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug
I will also say that if you just keep writing more code you can often achieve understanding through repetition. Honestly it's the way I got to the point where actual computer science theory started to stick: I did a ton of code katas on sites like CodeWars and Leetcode - I'd start with the ones with the highest completion rate, then try and brute force my way through it, googling anything I didn't understand.

This isn't perfect, but at least for me I really struggle with theory when I don't have a way to tie it to concrete examples.

Example: Dependency Injection seemed like the dumbest pattern in the world to me like 9 months ago. My only experiences with it had been the sort of 'magic pile of bullshit' added to big web framework startup sections, and it just seemed mystical to me. Why are you adding all these things here? Why not just use them where they're needed? Your code looks awful! Sure, I could read about why they were useful, but honestly it didn't click until:

I'm working on an escalation module for a service I own and part of that is mail; I have a mailer class that handles all my mail workflows, which are pretty minor. When I wrote this originally I just had singletons setup for the Mailer class, and no unit tests because my mentor at the time didn't think it was important. Now I'm coming back to my code after a month or so away from it and my immediate thought was 'well gently caress, how do I know this WORKS?'

'I guess I can do a dry-run of our stuff? Well poo poo, dry-runs don't process mail, on purpose, to avoid any accidental mailing.'

'Huh. I need like a 'send-mail-anyway-dry-run' option...But that seems like a weird edge case. What if I added mailing to the dry run, and then just had a neutered option? Well that'd be hard to do because I'd need to ohhhhh make a new mailer class with the send functionality nerfed and inject the dependency.'

The most obvious problem here is I should have been pushed to test my code when I originally wrote it, but now I know and will improve over time.

Adbot
ADBOT LOVES YOU

duck monster
Dec 15, 2004

Can someone tell me whats going on with this "fluent interface" poo poo.

https://github.com/kkroening/ffmpeg-python

Because it kind of makes me feel like bullying a javascript coder, and I'm not entirely sure why.

Is this actually a *thing* in python now? or is this dude just abusing import semantics? Cos it doesnt *look* pythonic, but I'm old and have bad knees and dont like kids on lawns or something.

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