I am migrating a project I have from being littered with globals variables to actually have a structure defined by classes defined in a separate module. This is my first time really using OOP so want to understand if it is safe to re-define an instance of a Class or if my code is missing something.
At the top of my code, I import my module -
import NHLGameEvents
config = configparser.ConfigParser()
config.read('config.ini')
TEAM_BOT = config['DEFAULT']['TEAM_NAME']
I then build two Team objects (defined in my NHLGameEvents module).
game_today, game_info = is_game_today(get_team(TEAM_BOT))
awayteam_info = game_info["teams"]["away"]["team"]
awayteamobj_name = awayteam_info["name"]
awayteamobj_shortname = awayteam_info["teamName"]
awayteamobj_tri = awayteam_info["abbreviation"]
away_team_obj = NHLGameEvents.Team(
awayteamobj_name, awayteamobj_shortname, awayteamobj_tri, "away")
game_obj.register_team(away_team_obj, "away")
hometeam_info = game_info["teams"]["home"]["team"]
hometeamobj_name = hometeam_info["name"]
hometeamobj_shortname = hometeam_info["teamName"]
hometeamobj_tri = hometeam_info["abbreviation"]
home_team_obj = NHLGameEvents.Team(
hometeamobj_name, hometeamobj_shortname, hometeamobj_tri, "home")
game_obj.register_team(home_team_obj, "home")
home_team_obj.preferred = bool(home_team_obj.team_name == TEAM_BOT)
away_team_obj.preferred = bool(away_team_obj.team_name == TEAM_BOT)
In some instances, I want to reference these Team objects as preferred and other as opposed to home / away so I use a method defined in my Game class to retrieve that. Since my Game object knows about both of my Teams, the method in my Game class that returns this Tuple is -
def register_team(self, team, key):
"""Registers a team to the instance of the Game."""
if key not in ('home', 'away'):
raise AttributeError(
"Key '{}' is not valid - Team key can only be home or away.".format(key))
if len(self.teams) > 1:
raise ValueError(
"Too many teams! Cannot register {} for {}".format(team, self))
self.teams[key] = team
team.game = self
team.tv_channel = self.broadcasts[key]
def get_preferred_team(self):
"""Returns a Tuple of team objects of the preferred & other teams."""
if self.teams["home"].preferred is True:
return (self.teams["home"], self.teams["away"])
return (self.teams["away"], self.teams["home"])
I can then retrieve that information from anywhere in my script.
preferred_team_obj, other_team_obj = game_obj.get_preferred_team()
Is it safe to redefine these class instances (ex - home_team_obj also known as preferred_team_obj) or should I just use an if statement whenever I want to reference these, such as -
if home_team_obj.preferred:
# Do something with home_team_obj
else:
# Do something with away_team_obj
Just as a follow up to this question, it seems that is totally safe to refer to assign an object to another name for use later in the code with no issues (as per the example below).
preferred_team = game.preferred_team
preferred_homeaway = preferred_team.home_away
on_ice = json_feed["liveData"]["boxscore"]["teams"][preferred_homeaway]["onIce"]
players = json_feed["gameData"]["players"]
if recent_event(play):
get_lineup(game, event_period, on_ice, players)
Related
I have a class in Python that initializes the attributes of an environment. I am attempting to grab the topographyRegistry attribute list of my Environment class in a separate function, which when called, should take in the parameters of 'self' and the topography to be added. When this function is called, it should simply take an argument such as addTopographyToEnvironment(self, "Mountains") and append it to the topographyRegistry of the Environment class.
When implementing what I mentioned above, I ran into an error regarding the 'self' method not being defined. Hence, whenever I call the above line, it gives me:
print (Environment.addTopographyToEnvironment(self, "Mountains"))
^^^^
NameError: name 'self' is not defined
This leads me to believe that I am unaware of and missing a step in my implementation, but I am unsure of what that is exactly.
Here is the relevant code:
class EnvironmentInfo:
def __init__(self, perceivableFood, perceivableCreatures, regionTopography, lightVisibility):
self.perceivableFood = perceivableFood
self.perceivableCreatures = perceivableCreatures
self.regionTopography = regionTopography
self.lightVisibility = lightVisibility
class Environment:
def __init__(self, creatureRegistry, foodRegistry, topographyRegistery, lightVisibility):
logging.info("Creating new environment")
self.creatureRegistry = []
self.foodRegistry = []
self.topographyRegistery = []
self.lightVisibility = True
def displayEnvironment():
creatureRegistry = []
foodRegistry = []
topographyRegistery = ['Grasslands']
lightVisibility = True
print (f"Creatures: {creatureRegistry} Food Available: {foodRegistry} Topography: {topographyRegistery} Contains Light: {lightVisibility}")
def addTopographyToEnvironment(self, topographyRegistery):
logging.info(
f"Registering {topographyRegistery} as a region in the Environment")
self.topographyRegistery.append(topographyRegistery)
def getRegisteredEnvironment(self):
return self.topographyRegistry
if __name__ == "__main__":
print (Environment.displayEnvironment()) #Display hardcoded attributes
print (Environment.addTopographyToEnvironment(self, "Mountains"))#NameError
print (Environment.getRegisteredEnvironment(self)) #NameError
What am I doing wrong or not understanding when using 'self'?
Edit: In regard to omitting 'self' from the print statement, it still gives me an error indicating a TypeError:
print (Environment.addTopographyToEnvironment("Mountains"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Environment.addTopographyToEnvironment() missing 1 required positional argument: 'topographyRegistery'
Comments
Despite having def getRegisteredEnvironment(self): it wasn't indented, so it's not recognized as a class method.
self is a keyword used in conjunction with classes (class methods or attributes) - not functions. self is implied to be the instantiated object (eg a = Environment(...) -> self would refer to a) or the module's (I can't think of the proper term) class.
You didn't have your addTopographyToEnvironment class method defined.
In terms of your Environment class, you aren't using the variables you are passing to the class, so I made that change as well - I don't know if that was intentional or not.
As per your comment from the other answer, if you had def my_class_method(self) and you try to invoke it through an object with additional parameters, like so a = my_object(); a.my_class_method("Mountains"), you should get an error of the sorts, "2 positional arguments passed, expected 1.".
Your main problem is that you are doing Environment.class_method() and not creating an object from the class. Do a = Environment(whatever arguments here) to create an object from the class, then do a.addTopographyToEnvironment("Mountains") to do what you were going to do with "Mountains" and that object. What you have currently may be right, its just is missing the proper implementation, but the below article does a great job explaining the differences between all of them (Class Methods vs Static Methods vs Instance Methods), and is definitely worth the read.
class EnvironmentInfo:
def __init__(self, perceivableFood, perceivableCreatures, regionTopography, lightVisibility):
self.perceivableFood = perceivableFood
self.perceivableCreatures = perceivableCreatures
self.regionTopography = regionTopography
self.lightVisibility = lightVisibility
class Environment:
def __init__(self, creatureRegistry, foodRegistry, topographyRegistery, lightVisibility):
logging.info("Creating new environment")
self.creatureRegistry = creatureRegistry
self.foodRegistry = foodRegistry
self.topographyRegistery = topographyRegistery
self.lightVisibility = lightVisibility
def displayEnvironment(self):
creatureRegistry = []
foodRegistry = []
topographyRegistery = ['Grasslands']
lightVisibility = True
print (f"Creatures: {creatureRegistry} Food Available: {foodRegistry} Topography: {topographyRegistery} Contains Light: {lightVisibility}")
def addTopographyToEnvironment(self, environment):
return "Whatever this is supposed to return." + environment
def getRegisteredEnvironment(self):
return self.topographyRegistry
if __name__ == "__main__":
print (Environment.displayEnvironment()) #Display hardcoded attributes
print (Environment.addTopographyToEnvironment("Mountains"))#NameError
print (Environment.getRegisteredEnvironment()) #NameError
Object Instantiation In Python
With all that out of the way, I will answer the question as is posed, "Is there a way to grab list attributes that have been initialized using self and append data to them in Python?". I am assuming you mean the contents of the list and not the attributes of it, the attributes would be "got" or at least printed with dir()
As a simple example:
class MyClass:
def __init__(self, my_list):
self.my_list = my_list
if __name__ == "__main__":
a = MyClass([1, 2, 3, 4, 5])
print(a.my_list)
# will print [1, 2, 3, 4, 5]
a.my_list.append(6)
print(a.my_list)
# will print [1, 2, 3, 4, 5, 6]
print(dir(a.my_list))
# will print all object methods and object attributes for the list associated with object "a".
Sub Classing In Python
Given what you have above, it looks like you should be using method sub classing - this is done with the keyword super. From what I can guess, it would look like you'd implement that kind of like this:
class EnvironmentInfo:
def __init__(self, perceivableFood, perceivableCreatures, regionTopography, lightVisibility):
self.perceivableFood = perceivableFood
self.perceivableCreatures = perceivableCreatures
self.regionTopography = regionTopography
self.lightVisibility = lightVisibility
class Environment(EnvironmentInfo):
def __init__(self, creatureRegistry, foodRegistry, topographyRegistery, lightVisibility, someOtherThingAvailableToEnvironmentButNotEnvironmentInfo):
logging.info("Creating new environment")
super.__init__(foodRegistry, creatureRegistry, topographyRegistery, lightVisibility)
self.my_var1 = someOtherThingAvailableToEnvironmentButNotEnvironmentInfo
def displayEnvironment(self):
creatureRegistry = []
foodRegistry = []
topographyRegistery = ['Grasslands']
lightVisibility = True
print (f"Creatures: {creatureRegistry} Food Available: {foodRegistry} Topography: {topographyRegistery} Contains Light: {lightVisibility}")
def addTopographyToEnvironment(self, environment):
return "Whatever this is supposed to return." + environment
def getRegisteredEnvironment(self):
return self.topographyRegistry
def methodAvailableToSubClassButNotSuper(self)
return self.my_var1
if __name__ == "__main__":
a = Environment([], [], [], True, "Only accessible to the sub class")
print(a.methodAvailableToSubClassButNotSuper())
as the article describes when talking about super(), methods and attributes from the super class are available to the sub class.
Extra Resources
Class Methods vs Static Methods vs Instance Methods - "Difference #2: Method Defination" gives an example that would be helpful I think.
What is sub classing in Python? - Just glanced at it; probably an okay read.
Self represents the instance of the class and you don't have access to it outside of the class, by the way when you are calling object methods of a class you don't need to pass self cause it automatically be passed to the method you just need to pass the parameters after self so if you want to call an object method like addTopographyToEnvironment(self, newVal) you should do it like:
Environment.addTopographyToEnvironment("Mountains")
and it should work fine
I'm trying to return variable name, but i keep getting this:
<classes.man.man object at (some numbers (as example:0x03BDCA50))>
Below is my code:
from classes.man import man
def competition(guy1, guy2, counter1=0, counter2=0):
.......................
some *ok* manipulations
.......................
if counter1>counter2:
return guy1
bob = man(172, 'green')
bib = man(190, 'brown')
print(competition(bob , bib ))
Epilogue
If anyone want to, explain please what I can write instead of __class__ in example below to get variable name.
def __repr__(self):
return self.__class__.__name__
Anyway, thank you for all of your support
There are different ways to approach your problem.
The simplest I can fathom is if you can change the class man, make it accept an optional name in its __init__ and store it in the instance. This should look like this:
class man:
def __init__(number, color, name="John Doe"):
self.name = name
# rest of your code here
That way in your function you could just do with:
return guy1.name
Additionnally, if you want to go an extra step, you could define a __str__ method in your class man so that when you pass it to str() or print(), it shows the name instead:
# Inside class man
def __str__(self):
return self.name
That way your function could just do:
return guy1
And when you print the return value of your function it actually prints the name.
If you cannot alter class man, here is an extremely convoluted and costly suggestion, that could probably break depending on context:
import inspect
def competition(guy1, guy2, counter1=0, counter2=0):
guy1_name = ""
guy2_name = ""
for name, value in inspect.stack()[-1].frame.f_locals.items():
if value is guy1:
guy1_name = name
elif value is guy2:
guy2_name = name
if counter1 > counter2:
return guy1_name
elif counter2 > counter2:
return guy1_name
else:
return "Noone"
Valentin's answer - the first part of it at least (adding a name attribute to man) - is of course the proper, obvious solution.
Now wrt/ the second part (the inspect.stack hack), it's brittle at best - the "variables names" we're interested in might not necessarily be defined in the first parent frame, and FWIW they could as well just come from a dict etc...
Also, it's definitly not the competition() function's responsability to care about this (don't mix domain layer with presentation layer, thanks), and it's totally useless since the caller code can easily solve this part by itself:
def competition(guy1, guy2, counter1=0, counter2=0):
.......................
some *ok* manipulations
.......................
if counter1>counter2:
return guy1
def main():
bob = man(172, 'green')
bib = man(190, 'brown')
winner = competition(bob, bib)
if winner is bob:
print("bob wins")
elif winner is bib:
print("bib wins")
else:
print("tie!")
Python prints the location of class objects in memory if they are passed to the print() function as default. If you want a prettier output for a class you need to define the __repr__(self) function for that class which should return a string that is printed if an object is passed to print(). Then you can just return guy1
__repr__ is the method that defines the name in your case.
By default it gives you the object type information. If you want to print more apt name then you should override the __repr__ method
Check below code for instance
class class_with_overrided_repr:
def __repr__(self):
return "class_with_overrided_repr"
class class_without_overrided_repr:
pass
x = class_with_overrided_repr()
print x # class_with_overrided_repr
x = class_without_overrided_repr()
print x # <__main__.class_without_overrided_repr instance at 0x7f06002aa368>
Let me know if this what you want?
I'm super new to Python (I started about 3 weeks ago) and I'm trying to make a script that scrapes web pages for information. After it's retrieved the information it runs through a function to format it and then passes it to a class that takes 17 variables as parameters. The class uses this information to calculate some other variables and currently has a method to construct a dictionary. The code works as intended but a plugin I'm using with Pycharm called SonarLint highlights that 17 variables is too many to use as parameters?
I've had a look for alternate ways to pass the information to the class, such as in a tuple or a list but couldn't find much information that seemed relevant. What's the best practice for passing many variables to a class as parameters? Or shouldn't I be using a class for this kind of thing at all?
I've reduced the amount of variables and code for legibility but here is the class;
Class GenericEvent:
def __init__(self, type, date_scraped, date_of_event, time, link,
blurb):
countdown_delta = date_of_event - date_scraped
countdown = countdown_delta.days
if countdown < 0:
has_passed = True
else:
has_passed = False
self.type = type
self.date_scraped = date_scraped
self.date_of_event = date_of_event
self.time = time
self.link = link
self.countdown = countdown
self.has_passed = has_passed
self.blurb = blurb
def get_dictionary(self):
event_dict = {}
event_dict['type'] = self.type
event_dict['scraped'] = self.date_scraped
event_dict['date'] = self.date_of_event
event_dict['time'] = self.time
event_dict['url'] = self.link
event_dict['countdown'] = self.countdown
event_dict['blurb'] = self.blurb
event_dict['has_passed'] = self.has_passed
return event_dict
I've been passing the variables as key:value pairs to the class after I've cleaned up the data the following way:
event_info = GenericEvent(type="Lunar"
date_scraped=30/01/19
date_of_event=28/07/19
time=12:00
link="www.someurl.com"
blurb="Some string.")
and retrieving a dictionary by calling:
event_info.get_dictionary()
I intend to add other methods to the class to be able to perform other operations too (not just to create 1 dictionary) but would like to resolve this before I extend the functionality of the class.
Any help or links would be much appreciated!
One option is a named tuple:
from typing import Any, NamedTuple
class GenericEvent(NamedTuple):
type: Any
date_scraped: Any
date_of_event: Any
time: Any
link: str
countdown: Any
blurb: str
#property
def countdown(self):
countdown_delta = date_of_event - date_scraped
return countdown_delta.days
#property
def has_passed(self):
return self.countdown < 0
def get_dictionary(self):
return {
**self._asdict(),
'countdown': self.countdown,
'has_passed': self.has_passed,
}
(Replace the Anys with the fields’ actual types, e.g. datetime.datetime.)
Or, if you want it to be mutable, a data class.
I don't think there's anything wrong with what you're doing. You could, however, take your parameters in as a single dict object, and then deal with them by iterating over the dict or doing something explicitly with each one. Seems like that would, in your case, make your code messier.
Since all of your parameters to your constructor are named parameters, you could just do this:
def __init__(self, **params):
This would give you a dict named params that you could then process. The keys would be your parameter names, and the values the parameter values.
If you aligned your param names with what you want the keys to be in your get_dictionary method's return value, saving off this parameter as a whole could make that method trivial to write.
Here's an abbreviated version of your code (with a few syntax errors fixed) that illustrates this idea:
from pprint import pprint
class GenericEvent:
def __init__(self, **params):
pprint(params)
event_info = GenericEvent(type="Lunar",
date_scraped="30/01/19",
date_of_event="28/07/19",
time="12:00",
link="www.someurl.com",
blurb="Some string.")
Result:
{'blurb': 'Some string.',
'date_of_event': '28/07/19',
'date_scraped': '30/01/19',
'link': 'www.someurl.com',
'time': '12:00',
'type': 'Lunar'}
My question is about getter/setter-type functionality in Python. I have a class, Week_Of_Meetings, that takes a blob of data from my Google Calendar and does some calculations on it.
wom = Week_Of_Meetings(google_meetings_blob)
I want to be able to return something like:
wom.total_seconds_in_meetings() # returns 36000
But, I'm not understanding how the getters/setters-type #property decorator can help me do this. In Java, I would use member variables, but you don't interact with them the same way in Python. How can I return calculations without starting with them in the constructor?
Class Week_Of_Meetings:
def __init__(self, google_meetings_blob)
self.google_meetings_blob = google_meetings_blob
def get_meetings_list(self, google_meetings_blob):
meetings_list = []
for meeting_id, meeting in enumerate(self.google_meetings_blob, 1):
summary = self._get_summary(meeting)
start = parse(meeting['start'].get('dateTime', meeting['start'].get('date')))
end = parse(meeting['end'].get('dateTime', meeting['end'].get('date')))
duration = end - start
num_attendees = self._get_num_attendees(meeting.get('attendees'))
m = Meeting(meeting_id, summary, start, end, duration, num_attendees)
meetings_list.append(m)
return meetings_list
def _get_summary(self, meeting):
summary = meeting.get('summary', 'No summary given')
return summary
def _get_num_attendees(self, num_attendees):
if num_attendees == None:
num_attendees = 1 # if invited only self to meeting
else:
num_attendees = len(num_attendees)
return num_attendees
When I add self.total_seconds_in_meetings to the
__init__()
I get "NameError: global name 'total_seconds_in_meetings' is not defined." That makes sense. It hasn't been defined. But I can't define it when it's supposed to be the result of calculations done on the google_meetings_blob. So, I'm confused where the 'total_seconds_in_meetings' goes in the class.
Thank you for the help!
Of course Python has member variables. How would classes work without them? You can set and get any instance data via self, as you are already doing with self.google_meetings_blob in __init__.
I'm trying to do the following, define two classes whose instances mutually reference one another, like Users and Groups in the following exemple. A User can belong to several groups and a Group can contains several users. The actual data is stored in a database and there it is a simple matter of many-to-many relationship using foreign keys. No problem at all.
Afterward the data is loaded through an ORM and stored in instances of python objects. Still no problem at all as the ORM used (SQLAlchemy) manage backrefs.
Now I want to check that the python objects comply to some interface using zope.interface and zope.schema. That's where I get into troubles.
import zope.schema as schema
from zope.interface import Interface, implements
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
# user_list = schema.List(title = u"List of Users in this group", value_type = sz.Object(IUser))
class IUser(Interface):
name = schema.TextLine(title=u"User's name")
group_list = schema.List(title = u"List of Groups containing that user",
value_type = schema.Object(IGroup))
IGroup._InterfaceClass__attrs['user_list'] = zs.List(title = u"List of Users in this group", required = False, value_type = zs.Object(IUser))
class Group(object):
implements(IGroup)
def __init__(self, name):
self.name = name
self.user_list = []
class User(object):
implements(IUser)
def __init__(self, name):
self.name = name
self.group_list = []
alice = User(u'Alice')
bob = User(u'Bob')
chuck = User(u'Chuck')
group_users = Group(u"Users")
group_auditors = Group(u"Auditors")
group_administrators = Group(u"Administrators")
def add_user_in_group(user, group):
user.group_list.append(group)
group.user_list.append(user)
add_user_in_group(alice, group_users)
add_user_in_group(bob, group_users)
add_user_in_group(chuck, group_users)
add_user_in_group(chuck, group_auditors)
add_user_in_group(chuck, group_administrators)
for x in [alice, bob, chuck]:
errors = schema.getValidationErrors(IUser, x)
if errors: print errors
print "User ", x.name, " is in groups ", [y.name for y in x.group_list]
for x in [group_users, group_auditors, group_administrators]:
errors = schema.getValidationErrors(IGroup, x)
if errors: print errors
print "Group ", x.name, " contains users ", [y.name for y in x.user_list]
My problem is the commented line. I can't define IGroup using IUser because at that time IUser is not yet defined. I've found a workaround completing the definition of IGroup after the definition of IUser but that is not satisfying at all, because IUser and IGroup are defined in different source files and part of IGroup is defined in the file defining IUser.
Is there any proper way to do that using zope.schema ?
Modify the field after definition:
#imports elided
class IFoo(Interface):
bar = schema.Object(schema=Interface)
class IBar(Interface):
foo = schema.Object(schema=IFoo)
IFoo['bar'].schema = IBar
Martijn's answer seems a bit more graceful and self-documenting, but this is a bit more succinct. Neither is perfect (compared to say, Django's solution of using string names for foreign keys) -- pick your poison.
IMHO, it would be nice to specify a dotted name to an interface instead of an identifier. You could pretty easily create a subclass of schema.Object to this end for your own use, should you find that approach useful.
You could define a base, or abstract, interface for IUser:
class IAbstractUser(Interface):
name = schema.TextLine(title=u"User's name")
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
user_list = schema.List(
title=u"List of Users in this group",
value_type=schema.Object(IAbstractUser))
class IUser(IAbstractUser):
group_list = schema.List(
title=u"List of Groups containing that user",
value_type=schema.Object(IGroup))
Because IUser is a subclass of IAbstractUser, objects implementing the former also satisfy the latter interface.
Edit: You can always still apply sdupton's dynamic after-the-fact alteration of the IGroup interface after you defined IUser:
IGroup['user_list'].value_type.schema = IUser
I'd still use the Abstract interface pattern to facilitate better code documentation.