python: get object arg and check NPE in one line - python

I have a method foo() which eventually returns a Student named john.
#!/usr/bin/python3
from random import randint
class Student(object):
name = ""
def foo():
if randint(0,1) == 0:
student = Student()
student.name = 'john'
else:
student = None
return student
I want to get the name of the student returned by foo() (avoiding NPEs and avoiding several calls to foo()).
My actual solution is:
student = foo()
print(student.name if student is not None else None)
Is it possible to do this in one line, and without creating a temporary variable ?

You can use getattr(obj, 'attr_to_get', default_value).
In your example, the solution would be getattr(foo(), 'name', None).

Related

How to print actual name of variable class type in function?

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?

Is there a way to fix Name Error due to scope?

I have a function that creates a player object but when referencing the object, I get a NameError. I think it is happening due to local scope but global should fix it...
I just started out OOP and this code is working in the python shell but it is not working in script mode.
endl = lambda a: print("\n"*a)
class Score:
_tie = 0
def __init__(self):
self._name = ""
self._wins = 0
self._loses = 0
def get_name(self):
print
self._name = input().upper()
def inc_score(self, wlt):
if wlt=="w": self._wins += 1
elif wlt=="l": self._loses += 1
elif wlt=="t": _tie += 1
else: raise ValueError("Bad Input")
def player_num(): #Gets number of players
while True:
clear()
endl(10)
print("1 player or 2 players?")
endl(5)
pnum = input('Enter 1 or 2: '.rjust(55))
try:
assert int(pnum) == 1 or int(pnum) == 2
clear()
return int(pnum)
except:
print("\n\nPlease enter 1 or 2.")
def create_player(): #Creates players
global p1
p1 = Score()
yield 0 #stops here if there is only 1 player
global p2
p2 = Score()
def pr_(): #testing object
input(p1._wins)
input(p2._wins)
for i in range(player_num()):
create_player()
input(p1)
input(p1._wins())
pr_()
wherever I reference p1 I should get the required object attributes but I'm getting this error
Traceback (most recent call last):
File "G:/Python/TicTacTwo.py", line 83, in <module>
input(p1)
NameError: name 'p1' is not defined
Your issue is not with global but with the yield in create_player(), which turns the function into a generator.
What you could do:
Actually run through the generator, by executing list(create_player()) (not nice, but works).
But I suggest you re-design your code instead, e.g. by calling the method with the number of players:
def create_player(num): #Creates players
if num >= 1:
global p1
p1 = Score()
if num >= 2:
global p2
p2 = Score()
If you fix this issue, the next issues will be
1) input(p1) will print the string representation of p1 and the input will be lost, you probably want p1.get_name() instead.
2) input(p1._wins()) will raise TypeError: 'int' object is not callable
I will redesign the app to introduce really powerful python constructs that may help you when getting into OOP.
Your objects are going to represent players, then don't call them Score, call them Player.
Using _tie like that makes it a class variable, so the value is shared for all the players. With only two participants this may be true but this will come to hurt you when you try to extend to more players. Keep it as a instance variable.
I am a fan of __slots__. It is a class special variable that tells the instance variables what attributes they can have. This will prevent to insert new attributes by mistake and also improve the memory needed for each instance, you can remove this line and it will work but I suggest you leave it. __slots__ is any kind of iterable. Using tuples as they are inmutable is my recomendation.
Properties are also a really nice feature. They will act as instance attribute but allow you to specify how they behave when you get the value (a = instance.property), assign them a value (instance.property = value), or delete the value (del instance.property). Name seems to be a really nice fit for a property. The getter will just return the value stored in _name, the setter will remove the leading and trailing spaces and will capitalize the first letter of each word, and the deletter will set the default name again.
Using a single function to compute a result is not very descriptive. Let's do it with 3 functions.
The code could look like this:
# DEFAULT_NAME is a contant so that we only have to modify it here if we want another
# default name instead of having to change it in several places
DEFAULT_NAME = "Unknown"
class Player:
# ( and ) are not needed but I'll keep them for clarity
__slots__ = ("_name", "_wins", "_loses", "_ties")
# We give a default name in case none is provided when the instance is built
def __init__(self, name=DEFAULT_NAME):
self._name = name
self._wins = 0
self._loses = 0
self._ties = 0
# This is part of the name property, more specifically the getter and the documentation
#property
def name(self):
""" The name of the player """
return self._name
# This is the setter of the name property, it removes spaces with .strip() and
# capitalizes first letters of each word with .title()
#name.setter
def name(self, name):
self._name = name.strip().title()
# This is the last part, the deleter, that assigns the default name again
#name.deleter
def name(self):
self._name = DEFAULT_NAME
def won(self):
self._wins += 1
def lost(self):
self._loses += 1
def tied(self):
self._ties += 1
Now that's all we need for the player itself. The game should have a different class where the players are created.
class Game:
_min_players = 1
_max_players = 2
def __init__(self, players):
# Check that the number of players is correct
if not(self._min_players <= players <= self._max_players):
raise ValueError("Number of players is invalid")
self._players = []
for i in range(1, players+1):
self._players.append(Player(input("Insert player {}'s name: ".format(i))))
#property
def players(self):
# We return a copy of the list to avoid mutating the inner list
return self._players.copy()
Now the game would be created as follows:
def new_game():
return Game(int(input("How many players? ")))
After that you would create new methods for the game like playing matches that will call the players won, lost or tied method, etc.
I hope that some of the concepts introduced here are useful for you, like properties, slots, delegating object creation to the owner object, etc.

Calling a function from a class in main

I seem to be making a stupid mistake that I cant find. Im simply trying to call my functions from my record class and having an invalid syntax error despite looking at sample code and trying to emulate the syntax.
Ive tried following tutorials and calling the function in every which way so the problem may not be in the calling of the function but something else I feel.
class definitions
class record:
def __init__(self,telephone,lastname,firstname):
self.telephone = telephone
self.lastname = lastname
self.firstname = firstname
def addrecord(self,x,y,z):
x = input('Enter telephone number')
y = input('Enter lastname')
z = input('Enter firstname')
phonebook.append(record(x,y,z))
return
def deleterecord(self,x):
phonebook[x-1].pop
return
Main
phonebook = record[]
addrecord(515,'fin','matt')
print(phonebook[0].firstname)
deleterecord(1)
print(phonebook[0].firstname)
If all this works I expect the output to be
"matt"
"null"
There are a number of problems with your code:
you are defining phonebook otuside of the class
in deleterecord you should call phonebook.pop(x).
there should be two classes that handle the phonebook and records, and the record could be modeled using a namedtuple.
there are syntax errors like calling record[] which is not valid Python.
Alternative implementation:
from collections import namedtuple
PhoneRecord = namedtuple("PhoneRecord", ['firstname', 'lastname', 'telephone'])
class PhoneBook:
def __init__(self):
self._phonebook = []
def addrecord(self, record):
self._phonebook.append(record)
return self._phonebook.index(record)
def deleterecord(self, i):
self._phonebook.pop(i)
phonebook = PhoneBook()
record_index = phonebook.addrecord(PhoneRecord(firstname="matt", lastname="snow", telephone="25512521"))
print(phonebook._phonebook)
phonebook.deleterecord(record_index)
print(phonebook._phonebook)
which will yield in the console:
[PhoneRecord(firstname='matt', lastname='snow', telephone='25512521')]
[]
The simplified version of your question is, given code
records = []
records.append("matt")
print(records[0])
del records[0]
print(records[0])
why don't I get the following output
"matt"
None
Instead, you get an IndexError exception.
The reason is that you are accessing an element beyond the size of the list, and Python handles this by raising an exception rather than returning None.

Trying to use random numbers to generate an outcome for Class module

I am having trouble returning to __mood field to generate a random mood for the animal objects. I don't know how to make it work, so what I have been trying to do is define it in the program titled animals.py
I have two programs: animals.py and animalgenerator.py
The animal generator asks for user input and produces a list that looks like:
What type of animal would you like to create? Truman
What is the animal's name? Tiger
Would you like to add more animals (y/n)? n
Animal List
-----------
Tiger the Truman is hungry
So far my program has worked, but it won't produce the moods.
__mood is a hidden attribute for the animal object.
check_mood: this method should generate a random number between 1
and 3.
The random number will be used to set one of three moods:
If the number is 1, the __mood field should be set to a value of “happy”.
If the number is 2, the __mood field should be set to a value of “hungry”.
If the number is 3, the __mood field should be set to a value of “sleepy”.
Finally, this method should return the value of the __mood field
Here is what I have on animals.py
class Animal:
# The __init__ method initializes the attributes
def __init__(self, name, mood, type):
self.__name = name
self.__mood = mood
self.__animal_type = type
def _animal_type(self, type):
self.__animal_type = type
def __name(self, name):
self.__name = name
def __mood(self, mood):
for i in range():
if random.randint(1, 3) == 1:
self.__mood = 'happy'
if random.randint(1, 3) == 2:
self.__mood = 'hungry'
if random.randint(1, 3) == 3:
self.__mood = 'sleepy'
else:
self.__mood = 'happy'
def get_animal_type(self):
return self.__animal_type
def get_name(self):
return self.__name
def check_mood(self):
return self.__mood
Here is what I have for animalgenerator.py
# This program tests the Animal class.
import animals
print("Welcome to the animal generator!")
print("This program creates Animal objects.")
def main():
# Get the animal data
animal_list = []
find_info = True
while(find_info):
_animal_type = input('\nWhat type of animal would you like to create? ')
__name = input('What is the animals name? ')
more_animals = input('Would you like to add more animals (y/n)? ')
if (more_animals != 'y'):
find_info = False
# Create an instance of animal class
animal_list.append(animals.Animal(_animal_type, __name, __mood))
animal = animals.Animal(_animal_type, __name, __mood)
# Display the data that was entered.
print('\nAnimal List\n')
print('------------- \n')
for animal in animal_list:
print('' + animal.get_animal_type() + ' the ' + animal.get_name() + ' is ' + animal.check_mood() + '\n')
# Call the main function
main()
A couple of thoughts: First, in __mood you have for i in range(): but range requires at least 1 argument. I think you probably don't want that at all, since there's no reason to be looping there that I can see.
Second, you probably need not generate a new random number for each check. If you generate the random int from 1 to 3 a single time and see if it's 1, 2, or 3 you should be able to set the mood you want.
Third, check_mood doesn't ever call __mood to have it generate set a new mood. Also, the way I read your assignment, er, requirements, you're supposed to generate the random number in check_mood then pass it to __mood instead of generating it within.
Forth, and probably more important than many of the above, particularly the third point, __mood can't be both a method name and attribute name. Probably you don't want __mood to be a method and just have the body of it in check_mood.
I believe this method can be written elegantly in 1-2 lines:
def __setmood(self):
self.__mood = ('happy', 'hungry', 'sleepy')[random.randint(0, 2)]
return self.__mood
But aside from that, I don't think you should use the same name for your method and your instance variable. When you do an assignment like self.__mood = 'happy', you actually overwrite the binding to the method of your object. In other words, you can't call the self.__mood() method anymore even from within the class...
For example, the following code will raise a TypeError ('str' object is not callable):
class X:
def __mood(self):
self.__mood = 'happy'
def callmood(self):
self.__mood()
return self
X().callmood().callmood()

python unbound method again

This gets me into difficult time (sorry, i am still very new to python)
Thank you for any kind of help.
The error
print Student.MostFrequent() TypeError: unbound method
MostFrequent() must be called with
Student instance as first argument
(got nothing instead)
This Student.MostFrequent() is called all the way in the end (last line) and the def is last def in the class
EDITED - Naming convention
My long code
import csv
class Student:
sports = []
ftopics = []
stopics = []
choice_list = []
choice_dict = {}
def __init__(self, row):
self.lname, self.fname, self.ID, self.gender, self.sport, self.movie, self.movieyr, self.country, self.ftopic, self.stopic = row
self.sports.append(self.sport)
self.ftopics.append(self.ftopic)
self.stopics.append(self.stopic)
def print_information(self):
return (self.lname, self.fname, self.ID, self.gender)
def print_first(self):
return (self.lname, self.fname, self.sport)
def print_second(self):
return (self.lname, self.fname, self.movie, self.movieyr)
def print_third(self):
return (self.lname, self.fname, self.country)
def print_fourth(self):
return (self.lname, self.fname, self.ftopic, self.stopic)
def most_frequent(self):
for choice in self.choice_list:
self.choice_dict[choice] = self.choice_dict.get(choice, 0) + 1
self.mostFrequent = sorted([(v, k) for k, v in self.choice_dict.items()], reverse=True)
print self.mostFrequent
reader = csv.reader(open('new_mondy_csc_data_revise.csv'), delimiter=',', quotechar='"')
header = tuple(reader.next())
print "%-17s|%-10s|%-6s|%s" %header[:4]
print "-" * 45
students = list(map(Student, reader)) # read all remaining lines
for student in students:
print "%-17s|%-10s|%-6s|%3s" % student.print_information()
print "%-17s|%-10s|%s" %(header[0],header[1],header[4])
print "-" * 45
for student in students:
print "%-17s|%-10s|%s" %student.print_first()
print "%-17s|%-10s|%-16s|%s" %(header[0],header[1],header[5],header[6])
print "-" * 45
for student in students:
print "%-17s|%-10s|%-16s|%s" % student.print_second()
print "%-17s|%-10s|%s" %(header[0],header[1],header[7])
print "-" * 45
for student in students:
print "%-17s|%-10s|%s" %student.print_third()
print "%-17s|%-10s|%-15s|%s" %(header[0],header[1],header[8],header[9])
print "-" * 45
for student in students:
print "%-17s|%-10s|%-16s|%s" % student.print_fourth()
k = len(students)
# Printing all sports that are specified by students
for s in set(Student.sports): # class attribute
print s, Student.sports.count(s), round(((float(Student.sports.count(s)) / k) *100),1)
# Printing sports that are not picked
allsports = ['Basketball','Football','Other','Baseball','Handball','Soccer','Volleyball','I do not like sport']
allsports.sort()
for s in set(allsports) - set(Student.sports):
print s, 0, '0%'
Student.choice_list = Student.sports
X = Student()
X.most_frequent()
#class Search(Student):
# def __init__(self):
# Student.__init__
first read PEP-8 on naming conventions:
Method Names and Instance Variables
Use the function naming rules: lowercase with words separated by
underscores as necessary to improve readability.
second you are calling mostFrequest on the class Student, not an instance of it. Use the method on an instance instead:
student = Student(row)
student.MostFrequent()
use Student().MostFrequent()
edit:
beware that you use class attributes and this is dangerous. here an example:
>>> class Person:
... name = None
... hobbies = []
... def __init__(self, name):
... self.name = name
...
>>> a = Person('marco')
>>> b = Person('francesco')
>>> a.hobbies.append('football')
>>> b.hobbies
['football']
>>> a.name
'marco'
>>> b.name
'francesco'
>>> a.name = 'mario'
>>> b.name
'francesco'
>>> a.name
'mario'
>>>
as you can see i modify marco's hobbies and francesco's hobbies are modified consequentially.
What you probably want is to define most_frequent as a classmethod:
#classmethod
def most_frequent(cls):
for choice in cls.choice_list:
cls.choice_dict[choice] = cls.choice_dict.get(choice, 0) + 1
cls.mostFrequent = sorted([(v, k) for k, v in cls.choice_dict.items()], reverse=True)
return cls.mostFrequent
First, I recommend making function names lower case only.
The error you get results from the usage of MostFrequent as a static method. For this to work, you need to explicitly pass an instance of Student as first argument.
If called directly on an instance of Student, the instance will implicitly be passed as first argument.
Consider using the staticmethod decorator for static usage of functions.
You only rarely call methods on a class definition (Student)
Almost always, you create an instance of the class
someStudent = Student(someRow)
Then you call the method on the instance ("object"), someStudent.
someStudent.MostFrequent()
Student.MostFrequent means You're trying to use static method, not instance method. So You must first create instance by calling Student() and then call MostFrequent() on it.
P.S.: If this is not part of some arcane project, I urge you to follow PEP 8 and use most_frequent as method name.
in your class def, the method definition
def MostFrequent(self,mostFrequent):
has the extra variable mostFrequent that you probably don't want there. Try changing to :
def MostFrequent(self):

Categories