-
Notifications
You must be signed in to change notification settings - Fork 714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python style fixes #484
Python style fixes #484
Conversation
Initializers should use property setters like everyone else. The default value of None can be applied with the existing default parameters instead of the manually setting internal values to None.
A sensible default for a list property is an empty list. This change simplifies existing code because we no longer need to turn a None into [] when using an add() method for the first time, and checks in get()s are simpler.
- move import to top of file for consistency - simplify __init__ logic: make it more clear that there are two categories of input (old v. new-style), and take advantage of default Nones.
Codecov Report
@@ Coverage Diff @@
## master #484 +/- ##
========================================
Coverage ? 85.1%
========================================
Files ? 30
Lines ? 920
Branches ? 108
========================================
Hits ? 783
Misses ? 84
Partials ? 53
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious as to what this PR really accomplishes. There seems to be a pattern in these classes:
- assign a "private" value(s) to
None
(corresponds to a public property) - if a corresponding argument was not passed in to
__init__
, call the setter
This pattern is also what is shown in the Python docs on properties:
class Parrot:
def __init__(self):
self._voltage = 100000
@property
def voltage(self):
"""Get the current voltage."""
return self._voltage
After briefly looking over your code, the main difference seems to be that all of the classes you modified now unnecessarily call the corresponding setter whether or not it is necessary. This was done by deleting the private variable (starting with _
) and assigning to a public one instead (which has the same name as the @property
decorated function).
For example, I took two classes, Ganalytics
and SubscriptionTracking
and added print statements after each setter.
GanalyticsNEW
andSubscriptionTrackingNEW
are your classes.GanalyticsORIGINAL
andSubscriptionTrackingORIGINAL
are the original classes.
>>> g = GanalyticsNEW()
enable setter
utm_source setter
utm_medium setter
utm_term setter
utm_content setter
utm_campaign setter
>>> g2 = GanalyticsORIGINAL()
>>> s = SubscriptionTrackingNEW()
enable setter
text setter
html setter
substitution_tag setter
>>> s2 = SubscriptionTrackingORIGINAL()
Of course, this happens only when each class falls back on the default None
arguments but, other than lines of code reduced, I'm curious as to how this is an improvement?
sendgrid/helpers/mail/category.py
Outdated
@@ -1,9 +1,7 @@ | |||
class Category(object): | |||
|
|||
def __init__(self, name=None): | |||
self._name = None | |||
if name is not None: | |||
self._name = name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Shouldn't this...
self._name = name
# be this?
self.name = name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix in next push.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You deleted this code already as well...
self._groups_to_display = None | ||
|
||
if group_id is not None: | ||
self._group_id = group_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Shouldn't this...
self._group_id = group_id
# be this?
self.group_id = group_id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the catch - will fix typo in next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how you are going to do this since you deleted this code already...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh shoot - I thought that my replacement code had the typo, not the original. Github's not too great on mobile but I'll poke at it soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah so with this and the other changes I kept the typo - my new code should use the setter and not assign directly to the private variables. Oops!
self._group_id = group_id | ||
|
||
if groups_to_display is not None: | ||
self._groups_to_display = groups_to_display |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Shouldn't this...
self._groups_to_display = groups_to_display
# be this?
self.groups_to_display = groups_to_display
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix in next push.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You deleted this code already too...
Hey @delirious-lettuce, thanks for the detailed code review. I'll push the changes you pointed out once I get a little time. Calling the setters on However, I hadn't seen the docs example you showed, and I found more examples elsewhere. Upon further looking I can't find any that show it "my way", but neither can I find anything that explains why direct access is used instead of the setter. (I also couldn't find a PEP that introduced properties, so my Google-fu needs work.) I'm happy to close this if it's not best practice, but I'm curious about why direct access is considered better here. I know there's overhead in calling setters, but performance doesn't seem to be a concern since we have stub getters and setters in places where direct access could be used. In my view it makes things simpler with a minor performance penalty. |
I don't work for SendGrid or have any power over this PR, I was just reading through different PRs and became curious when I read yours. You are free to ignore my "review"! That said, the main reason I became interested in your PR was that it was written differently than other code using |
@delirious-lettuce No, no worries! I appreciate the pair of eyes (plus Matt and Elmer are pretty overworked right now from Hacktoberfest). You're definitely right about standard practice, I suppose I'd never noticed. It still feels like the right thing to do to me, but I don't like going against convention. If someone else could elaborate a bit on why examples use direct access it'd be awesome - maybe we can let this sit until then? IMO, we probably shouldn't be using properties at all, since there's no error checking or special behavior in most places and the whole point of properties is that you can put them in as necessary without changing the interface. Without them we'd have the benefits of both approaches. But that's kind of beside the point since it would be too much trouble to remove them, we'd lose docstrings, etc. Here's how I'd sum up pros/cons: Pros:
Cons:
Ultimately though if it's unexpected enough to make you do a double-take I think that overrides whatever subjective simplicity it brings. I'll leave it up for now to get more opinions but my guess is it'll get closed. |
Sure, no problem.
Ya, you're right there isn't anything special in the getters/setters yet but I guess different functionality could be easily added in the future (if necessary). And yes, it also seems like a bit much to be removing them at this point to.
I didn't want to make it seem like this was a big deal or anything (I didn't do any profiling to measure the difference), it was just that I couldn't find a lot of differences between the two different versions.
I don't think I have enough experience to have that decision rest solely on my opinion of how it was written. As I've said, I was really just curious that this could be a better way to write it and was trying to figure out why. I'm still learning new things about Python on an almost daily basis and reading other developers code is one of the best ways that I've found to improve my own skills. Thanks for your time! |
Thanks @delirious-lettuce! I learned something new as well :) |
I am also of the mind that having setters and getters is ideal, because you can defer any future responsibility for modifying the parameters in the same place. this keeps the code simpler, when you're debugging - whereas having a direct access in one place with logic for how to set that param (defaulting, any ETL, or other modifications) and then also having a setter/getter that might have to do that seems redundant. Having said all of that, it always "feels wrong" when making a basic class that is really just a clean way to organize and access parameters because it is so redundant. I will defer to whatever the master, @thinkingserious, says here - my personal preference, verbose as it is, would be to make it a standard to have the getters and setters - which forces the issue for future code, as insurance against having parameters act one way on init and another on obj.param. This also has the impact of simplifying testing. |
I also feel the need to add - THANK YOU both @gabrielkrell and @delirious-lettuce for having a great conversation about this here. I was able to quickly join the conversation and throw my $.02 in, whether it was helpful or not is up for debate - but you saved me TONS of time and provided great information that we can refer to in the future. I really you both and your time spent here. |
Thanks for the input, that makes a lot of sense! EDIT: I've been having fun trying to do some reviews on these repos! |
@delirious-lettuce it is a tough call - on one hand, we are actually asking quite a bit - "Write all this seemingly redundant code to save what is likely 2 lines of duplication". In the future though, we can easily say "Add this logic to the getter on this object" and there is no confusion about what we are saying in the issue or where the logic should be. For a project with, as of this month, hundreds of contributors - I can definitely see the value in being explicit. |
I would agree 100% (but my vote doesn't really matter haha) |
Thanks to delirious-lettuce - these were in the original code but should have been caught here. *Probably* don't want to bypass the setters.
That wasn't too bad.
@@ -1,5 +1,6 @@ | |||
class TrackingSettings(object): | |||
"""Settings to track how recipients interact with your email.""" | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidental change in merge commit - added a newline between class docstring and __init__
in several places, but this is consistent with the rest of the code (and probably some PEP rule).
We have not been able to merge your Pull Request, but because you are awesome - we wanted to make sure you could still get a SendGrid Hacktoberfest shirt. Please go fill out our swag form before Nov 5th and we will send the shirt! (We know that you might have tried this before and it didn’t work, sorry about that!) You have till Nov 5th to fill out this form in order to get the Hacktoberfest shirt!Thank you for contributing during Hacktoberfest! We hope to see you in the repos soon! Just so you know, we always give away a SendGrid shirt for your first ever non-Hacktoberfest PR that gets merged. |
Hello @gabrielkrell, |
Hey @gabrielkrell - wanna chat as a contributor? You can grab a time on my calendar Will be great to catch up :) |
@mbernier I'd love to - sorry for taking so long. I grabbed a slot Tuesday PM. |
Checklist
Short description of what this PR does:
These things were just bugging me a bit. There's probably a "terser is better" or something in the Python Zen.
Note: I think existing tests should cover this since I didn't change any functionality, but I'll let Codecov yell at me if not. Tests pass with
tox
.