This question already has answers here:
What is the difference between class and instance attributes?
(5 answers)
Closed 4 months ago.
I'm learning how classes works and I got stuck on a thing which I can't explain, I also didn't find a solution on the Internet so here am I with my first question on StackOverflow.
class Swords:
damage = 5
Here is a class with only one line, with attribute damage.
Let's make an instance of the class:
sharp_sword = Swords()
And here is the moment of truth.
My class doesn't have an init function, so right now my sharp_sword.__dict__ is empty - my object of the class has an empty namespace, it doesn't have any attributes or methods. Well, we can create an attribute since we already defined it in the class itself:
sharp_sword.damage = 10
Now if we print(sharp_sword.damage) the return will be 10, if we print(Swords.damage), the return will be 5. That means that our instance of the class has its own attribute - and it is without any init inside my class.
I can create another object, set its damage to 15, and I will have two objects with different damage. Since we didn't change damage in class, if we print(Swords.damage), the return will be 5.
The question coming by itself - why then should I use __init__ inside my class to set properties for objects if I can do it without it with even fewer lines of code in my class?
class SwordsWithoutInit:
damage = 5
class SwordsWithInit:
def __init__(self):
self.damage = 5
big_sword = SwordsWithoutInit()
big_sword.damage = 10
sharp_sword = SwordsWithInit()
sharp_sword.damage = 20
print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'sharp sword (class {type(sharp_sword)}) damage equals {sharp_sword.damage}')
# it both works the same way
# note that even if you create another object of class without init, it still will work the same as with init, there won't be any errors
another_sword = SwordsWithoutInit()
another_sword.damage = 33
print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'another sword (class {type(another_sword)}) damage equals {another_sword.damage}')
print(f'Class without init still will have damage attribute equals to {SwordsWithoutInit.damage} in itself - we didnt change it')
'''
output:
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
#sharp sword (class <class '__main__.SwordsWithInit'>) damage equals 20
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
another sword (class <class '__main__.SwordsWithoutInit'>) damage equals 33
Class without init still will have damage attribute equals to 5 in itself - we didnt change it
'''
The way you've set this up, via some odd sequence you'll sort of get the result you expect either way, but it's mostly due to int being immutable.
So, let's look at a mutable type instead:
class Sword:
pass
class Spear:
pass
# All players get a sword by default
class Items:
weapons = [Sword()]
player1_items = Items()
player2_items = Items()
# give player2 a spear
player2_items.weapons.append(Spear())
# player1 should still just have a sword right?
# whoops! they have a spear too!
print(player1_items.weapons)
live example
You might now ask "well then I'll just use __init__ if I have mutable types." and I suppose that would work for the cases you're describing. It's just a strange way of structuring the code and will make it harder to read over time. To answer the question why you should favor assigning to self even "if I can do it without it with even less lines of code"..
Minimizing the number of lines isn't a good motivation to do something.
Keeping your code readable, maintainable, and fairly consistent is a good motivation.
Listen, init func is very important. With its help you can set the attributes while you create the object. like
sword = Sword(5)
This function is very useful, when you have multiple attributes to set coz you won't want to set each attribute separetly, it is also very useful when you have to make many object of the same the class, you won't want to write another extra lines for each just setting the attributes.
Hope that helps!!
Why do we use __init__ in Python?
Using __init__ is simpler
At first glance, your approach does seem shorter. By not implementing an __init__ method, your class definition becomes one line shorter and looks much cleaner:
class NoInitSword:
damage = 5
However, it actually requires a lot more code. You now have to write two lines each time you want a sword with a different value:
butter_knife = NoInitSword()
butter_knife.damage = 1
This may not seem like a big deal, but it can quickly get messy. For example, if you wanted to create a list of swords:
big_sword = NoInitSword()
big_sword.damage = 10
sharp_sword = NoInitSword()
sharp_sword.damage = 20
normal_sword = NoInitSword()
big_sharp_sword = NoInitSword()
big_sharp_sword.damage = 30
sword_collection = [big_sword, sharp_sword, normal_sword, big_sharp_sword]
Or:
sword_collection = [NoInitSword(), NoInitSword(), NoInitSword(), NoInitSword()]
sword_collection[0].damage = 10
sword_collection[1].damage = 20
sword_collection[3].damage = 30
A better approach
Fortunately, Python provides us a way of creating an object and setting its attributes to whatever values we want—all in just one line. The only thing we have to do is define an __init__ method.
This method takes arguments that we can use when initializing the object. For instance:
class Sword:
def __init__(self, damage=5):
self.damage = damage
This allows you to easily create new objects in one line:
big_sword = Sword(10) # big_sword.damage == 10
sharp_sword = Sword(20) # sharp_sword.damage == 20
normal_sword = Sword() # normal_sword.damage == 5
And that list example from earlier? Here's what it looks like with an __init__ method:
sword_collection = [Sword(10), Sword(20), Sword(), Sword(30)]
Related
This question already has answers here:
Adding a method to an existing object instance in Python
(19 answers)
Closed 4 years ago.
Q: Is there a way to alter a method of an existing object in Python (3.6)? (By "method" I mean a function that is passed self as an argument.)
Example
Let's say I have a class Person having some very useful method SayHi():
class Person(object):
Cash = 100
def HasGoodMood(self):
return self.Cash > 10
def SayHi(self):
if self.HasGoodMood():
print('Hello!')
else:
print('Hmpf.')
>>> joe = Person()
>>> joe.SayHi()
Hello!
As you can see, the response of the person depends on their current mood computed by the method HasGoodMood(). A default person has good mood whenever they have more than 10$ cash on them.
I can easily create a person who does not care about the money and is happy all the time:
>>> joe.HasGoodMood = lambda: True
>>> joe.SayHi()
Hello!
>>> joe.Cash = 0
>>> joe.SayHi()
Hello!
Cool. Notice how Python knows that when using the original implementation of HasGoodMood, it passes silently self as the first argument, but if I change it to lambda: True, it calls the function with no arguments. The problem is: What if I want to change the default HasGoodMood for another function which would also accept self as a parameter?
Let's continue our example: what if I want to create a greedy Person who is only happy if they have more than 100$ on them? I would like to do something like:
>>> greedy_jack = Person()
>>> greedy_jack.HasGoodMood = lambda self: self.Cash > 100
TypeError: <lambda>() missing 1 required positional argument: 'self'
Unfortunately, this does not work. Is there some other way to change a method?
Disclaimer: The above example is just for demonstration purposes. I know that I could use inheritance or keep a cash threshold as a property of the Person. But that is not the point of the question.
Using some tips from:
Is it possible to change an instance's method implementation without changing all other instances of the same class?
you can do the following, by using the types module to assign a method to the object created without affecting the class. You need to do this because a function does not automatically receive the self object as the first variable, but a method does.
import types
joe = Person()
bob = Person()
joe.SayHi()
>>> Hello!
def greedy_has_good_mood(self):
return self.Cash > 100
joe.HasGoodMood = types.MethodType(greedy_has_good_mood, joe)
joe.SayHi()
>>> Hmpf.
bob.SayHi()
>>> Hello!
When you write a def in a class, and then call it on an instance, that's a method, and the mechanics of method-calling will fill in the self argument when you call it.
By assigning to HasGoodMood in your instance, you are not putting a new method there, but putting a function into the attribute. You can read the attribute to get the function, and call it, and though that looks like a method call, it's just calling a function that happens to be stored in an attribute. You won't get the self parameter supplied automatically.
But you already know what self is going to be, since you're assigning this function into one particular object.
greedy_jack.HasGoodMood = (lambda self=greedy_jack: self.Cash > 100)
This associates the function argument self with the current value of the variable greedy_jack.
Lambdas in Python can only be one line. If you needed a longer function, you could use a def instead.
def greedy_jack_HasGoodMood(self=greedy_jack):
return self.Cash > 100
greedy_jack.HasGoodMood = greedy_jack_HasGoodMood
For a less hacky solution, see Andrew McDowell's answer.
Inheritance is the way to go.
It can be something as simple as:
class Person(object):
Cash = 100
def HasGoodMood(self):
return self.Cash > 10
def SayHi(self):
if self.HasGoodMood():
print('Hello!')
else:
print('Hmpf.')
class newPersonObject(Person):
def HasGoodMood(self):
return self.Cash > 100
>>> greedy = newClassPerson()
>>> greedy.SayHi()
hmpf
When you do greedy_jack.HasGoodMood = lambda self: self.Cash > 100 you're somewhat doing the same thing. You're only overriding greedy_jacks attributes. Using the way mentioned about, you can create greedy people, happy people, forever unhappy people, hippies etc.
A better option in my opinion would be to accept cash a parameter while defining the object. Hence, you can dynamically make people greedy or normal. (not tested)
class Person(object):
def __init__(self, cash_to_be_happy):
self.cash = cash_to_be_happy
def HasGoodMood(self, has_money):
return has_money > self.cash
def SayHi(self, has_money):
if self.HasGoodMood(has_money):
print('Hello!')
else:
print('Hmpf.')
>>> joe = Person(100)
>>> joe.SayHi(150)
Hello!
>>> greedy_joe = Person(200)
>>> greedy_joe.SayHi(150)
Hmpf
I have created a class distance_neighbor in which one of the attributes is a list of objects of class Crime. That is the value for all attributes I get from database query result.
At first, I have set data_Crime list as the value for attribute **Crime on class distance_neighbor, and I used del to clear data_Crime list after used, so that the data_Crime list can used in the next loop.
This is my code:
conn = psycopg2.connect("dbname='Chicago_crime' user='postgres' host='localhost' password='1234'")
cur= conn.cursor()
minDistance=float(input("Nilai minimum distance : "))
cur.execute("""SELECT id_objek1, objek1, id_objek2, objek2, distance from tb_distance1 where distance<'%f'""" %(minDistance))
class Crime:
def __init__(self, id_jenis, jenis):
self.id_jenis=id_jenis
self.jenis=jenis
class distance_neighbor (Crime):
def __init__(self, distance, **Crime):
self.distance = distance
self.Crime = Crime
data_Crime =[]
data_distance = []
for id_objek1, objek1, id_objek2, objek2, distance in cur.fetchall():
data_Crime.append(Crime(id_objek1,objek1))
data_Crime.append(Crime(id_objek2,objek2))
data_distance.append(distance_neighbor(distance, data_Crime))
del data_Crime[:]
error Message:
data_distance.append(distance_neighbor(distance, data_Crime))
TypeError: __init__() takes exactly 2 arguments (3 given)
I have fixed my code using below answers guys, Thank you
This should be closer to what you want:
class Crime(object):
def __init__(self, id_jenis, jenis):
self.id_jenis=id_jenis
self.jenis=jenis
class DistanceNeighbor(object):
def __init__(self, distance, crimes):
self.distance = distance
self.crimes = crimes
data_distance = []
for id_objek1, objek1, id_objek2, objek2, distance in cur.fetchall():
crimes = [Crime(id_objek1,objek1), Crime(id_objek2,objek2)]
data_distance.append(DistanceNeighbor(distance, crimes))
Classes in Python 2 should always inherit from object. By convention, class names are in CamelCase.
The inheritance of DistanceNeighbor from Crime seems unnecessary. I changed this.
Attributes to instance should be lower case, therefore I used crimes instead of the very confusing reuse of the class name Crime.
This line:
def __init__(self, distance, **Crime):
takes your list of Crime instance apart as separate arguments.
In your case it means the __init__ receives:
distance, data_Crime[0], data_Crime[0]
this causes this error message:
TypeError: init() takes exactly 2 arguments (3 given)
The instantiation of Crime is pretty short. So, instead of the two appends you can create the list of the two Crime instances in one line:
crimes = [Crime(id_objek1,objek1), Crime(id_objek2,objek2)]
Since this creates a new list in each loop, there is no need to delete the list content in each loop, as you did with del data_Crime[:].
You've defined your __init__ method in distance_neighbor as taking arguments (self, distance, **Crime). The ** before Crime tells Python to pack up any keyword arguments you're passed into a dictionary named Crime. That's not what you're doing though. Your call is distance_neighbor(distance, data_Crime) where data_Crime is a list. You should just accept that as a normal argument in the __init__ method:
class distance_neighbor (Crime):
def __init__(self, distance, crime):
self.distance = distance
self.crime = crime
This will mostly work, but you'll still have an issue. The problem is that the loop that's creating the distance_neighbor objects is reusing the same list for all of them (and using del data_Crime[:] to clear the values in between). If you are keeping a reference to the same list in the objects, they'll all end up with references to that same list (which will be empty) at the end of the loop.
Instead, you should create a new list for each iteration of your loop:
for id_objek1, objek1, id_objek2, objek2, distance in cur.fetchall():
data_Crime = [Crime(id_objek1,objek1), Crime(id_objek2,objek2)]
data_distance.append(distance_neighbor(distance, data_Crime))
This will work, but there are still more things that you probably want to improve in your code. To start with, distance_neighbor is defined as inheriting from Crime, but that doesn't seem appropiate since it contains instance of Crime, rather than being one itself. It should probably inherit from object (or nothing if you're in Python 3 where object is the default base). You may also want to change your class and variable names to match Python convention: CamelCase for class names and lower_case_with_underscores for functions, variables and attributes.
def __init__(self, distance, **Crime):
**Crime is a keyword argument, and expects named arguments. You don't need that, remove the asterisks.
Also, rename the argument, it's very confusing that it has the same name as the class:
class distance_neighbor(Crime):
def __init__(self, distance, c):
self.distance = distance
self.Crime = c
I have the following example setup:
class Feet:
def __init__ (self, value = 0.0):
self.value = value
self.units = "f"
def feet(self):
return self.value
class Meters:
def __init__(self, value = 0.0):
self.value = value
self.units = "m"
def feet(self):
# This is probably not an accurate conversion.
return self.value * 2.54 * 10
class Distance (Feet, Meters):
def __init__(self, type = Feet()):
Feet.__init__(self)
Meters.__init__(self)
print type.feet() -- Prints 254.0
self = type
print self.feet() -- Prints 254.0
dist = Distance(Meters(10.0))
print dist.units -- Prints = "m"
print dist.value -- Prints = 0.0
print dist.feet() -- Prints = 0.0
I can't seem to understand why when I initialize the class to a Meters Class type, and assign it 10.0, I don't keep the 10.0. However the Units seem to have stayed correct. Am I missing something about how this is being setup?
My understanding is that I'm creating an "instance" of Meters, and assigning it to the "self" variable of Distance. If the self value couldn't be modified I could understand if my units was "f", but my units is "m" so it's obviously assigning the Meters class to self, but it's not taking the instantiated values, which I find quite odd.
To be honest I don't even know what I would google in this case, so I apologize I haven't done a whole lot of googling, most of what I found didn't apply at all to this type of problem.
Additionally, my plan was to basically "cast" it to the same type no matter what you passed in, for example for feet I would return the self instance for the Feet class, and in the Meters class I would return Feet(self.Value * 2.54 * 10) so I would always have my distance in Feet.
so for Feet feet becomes
def feet(self):
return self
for Meters feet becomes
def feet(self):
return Feet(self.value * 2.54 * 10)
To Recap, is there a reason that I'm able to pass in 1 of 2 classes as part of initialization, but it doesn't take my initialization parameters for that class?
It's really unclear to me why I can assign "self" in the distance class, and before it returns it appears to have the right initialization but upon returning it doesn't work right.
The thing is that you are inheriting from 2 classes Feet and Meters. Both classes have the same methods. In your Distance.__init__() method, you are overriding Feet's methods with Meters' methods when doing this:
Feet.__init__(self)
Meters.__init__(self)
What I would have done differently:
class Distance(object):
def __init__(self, meters=None, feet=None):
self.feet = feet
self.meters = meters
Then you can do something like:
distance = Distance(meters=Meters(12))
print distance.meters.value
print distance.meters.type
# Here do whatever you want with them
You can pass in the two objects at the same time. And do some other stuff with
the two objects if the are both different than None.
There's absolutely no reason to inherit from either Feet or Meters here, let alone both. This is a classic case of composition, rather than inheritance, especially since you are passing the units class as a parameter. Remove that subclassing, and in __init__ you can do self.type = type.
Other answers cover the problems you have with inheriting, but haven't covered your rebinding of self.
Inside a method (such as __init__), self is simply a local name bound to the instance. You are perfectly at liberty to rebind the name, but that simply makes self refer to something else. It doesn't affect the instance.
In this case, when __init__ returns the self name goes out of scope, but the original instance is assigned to dist just as though you hadn't rebound the name self.
Note that __init__ is an initializer, not a constructor. Python does also allow you to define a custom constructor for a class (__new__), and the constructor can change the object that is returned. However you don't need to use it here.
This line:
self = type
doesn't do what you think it does. You think this is an assignment statement, in which the object refered to by self takes on the attributes of type, a la C++.
Python doesn't have assignments in the same sense that other languages have.
What that line does is to bind the local name self to the object to which type is currently bound. It has absolutely no effect outside of Distance.__init__(), and virtually no effect on the object to which self was previously bound.
I'm making a game in pygame and I have made an 'abstract' class that's sole job is to store the sprites for a given level (with the intent of having these level objects in a list to facilitate the player being moved from one level to another)
Alright, so to the question. If I can do the equivalent of this in Python(code curtesy of Java):
Object object = new Object (){
public void overriddenFunction(){
//new functionality
};
};
Than when I build the levels in the game I would simply have to override the constructor (or a class/instance method that is responsible for building the level) with the information on where the sprites go, because making a new class for every level in the game isn't that elegant of an answer. Alternatively I would have to make methods within the level class that would then build the level once a level object is instantiated, placing the sprites as needed.
So, before one of the more stanch developers goes on about how anti-python this might be (I've read enough of this site to get that vibe from Python experts) just tell me if its doable.
Yes, you can!
class Foo:
def do_other(self):
print('other!')
def do_foo(self):
print('foo!')
def do_baz():
print('baz!')
def do_bar(self):
print('bar!')
# Class-wide impact
Foo.do_foo = do_bar
f = Foo()
g = Foo()
# Instance-wide impact
g.do_other = do_baz
f.do_foo() # prints "bar!"
f.do_other() # prints "other!"
g.do_foo() # prints "bar!"
g.do_other() # prints "baz!"
So, before one of the more stanch developers goes on about how anti-python this might be
Overwriting functions in this fashion (if you have a good reason to do so) seems reasonably pythonic to me. An example of one reason/way for which you might have to do this would be if you had a dynamic feature for which static inheritance didn't or couldn't apply.
The case against might be found in the Zen of Python:
Beautiful is better than ugly.
Readability counts.
If the implementation is hard to explain, it's a bad idea.
Yes, it's doable. Here, I use functools.partial to get the implied self argument into a regular (non-class-method) function:
import functools
class WackyCount(object):
"it's a counter, but it has one wacky method"
def __init__(self, name, value):
self.name = name
self.value = value
def __str__(self):
return '%s = %d' % (self.name, self.value)
def incr(self):
self.value += 1
def decr(self):
self.value -= 1
def wacky_incr(self):
self.value += random.randint(5, 9)
# although x is a regular wacky counter...
x = WackyCount('spam', 1)
# it increments like crazy:
def spam_incr(self):
self.value *= 2
x.incr = functools.partial(spam_incr, x)
print (x)
x.incr()
print (x)
x.incr()
print (x)
x.incr()
print (x)
and:
$ python2.7 wacky.py
spam = 1
spam = 2
spam = 4
spam = 8
$ python3.2 wacky.py
spam = 1
spam = 2
spam = 4
spam = 8
Edit to add note: this is a per-instance override. It takes advantage of Python's attribute look-up sequence: if x is an instance of class K, then x.attrname starts by looking at x's dictionary to find the attribute. If not found, the next lookup is in K. All the normal class functions are actually K.func. So if you want to replace the class function dynamically, use #Brian Cane's answer instead.
I'd suggest using a different class, via inheritance, for each level.
But you might get some mileage out of copy.deepcopy() and monkey patching, if you're really married to treating Python like Java.
I asked a similar, yet lousy, question very late last night (Access to instance variable, but not instance method in Python) that caused a fair bit of confusion. I'd delete it if I could, but I can't.
I now can ask my question more clearly.
Background: I'm trying to build a black-jack game to learn python syntax. Each hand is an instance of the Hand class and I'm now at the point where I'm trying to allow for hands to be split. So, when it comes time for a hand to be split, I need to create two new hand instances. Given that further splits are possible, and I want to reuse the same methods for re-splitting hands. I therefore (I think) need to dynamically instantiate the Hand class.
Following is a code snippet I'm using to block out the mechanics:
import os
os.system("clear")
class Hand():
instances=[]
def __init__(self, hand_a, name):
Hand.instances.append(self)
self.name = name
self.hand_a = hand_a
def show_hand(self):
ln = len(self.hand_a)
for x in range(ln):
print self.hand_a[x]
class Creation():
def __init__(self):
pass
def create_name(self):
hil = len(Hand.instances)
new_name = 'hand_' + str(hil + 1)
return(new_name)
def new_instance(self):
new_dict = {0: 'Ace of Clubs', 1: '10 of Diamonds'}
new_hand_name = {}
new_hand_name.setdefault(self.create_name(), None)
print new_hand_name
new_hand_name[0] = Hand(new_dict, self.create_name())
print new_hand_name[0]
hand = Hand("blah", 'hand')
hand_z = Hand("blah_z", 'hand_z')
creation = Creation()
creation.new_instance()
here is the output:
{'hand_3': None}
<__main__.Hand instance at 0x10e0f06c8>
With regard to the instance created by the following statement:
new_hand_name[0] = Hand(new_dict, self.create_name)
Is new_hand_name[0] new the variable that refers to the instance?
Or, is hand_3 the variable?
i.e. when calling an instance method, can I use hand_3.show_hand()?
First, to answer your questions: new_hand_name[0] is the variable that refers to the instance- more specifically, it is the value in the new_hand_name dictionary accessed by the key 0. The new_hand_name dictionary, if you printed it, would look like:
{'hand_3': None, 0: <__main__.Hand instance at 0x10e0f06c8>}
Adding the value of "hand_3" to the dictionary is unnecessary, but for that matter, so is the dictionary.
What you really want to do has nothing to do with dynamic instantiation of new classes, which has nothing to do with your problem. The problem is that a Hand might represent a single list of cards, but might also represent a list of lists of cards, each of which have to be played separately. One great way to solve this is to make a separation between a player and a hand, and allow a player to have multiple hands. Imagine this design (I'm also leaving out a lot of the blackjack functionality, but leaving a little in to give you an idea of how to work this in with the rest of the program).
def draw_random_card():
"""
whatever function returns a new card. Might be in a Deck object, depends on
your design
"""
# some code here
class Player:
def __init__(self):
self.hands = []
def deal(self):
"""add a random hand"""
self.hands.append(Hand([draw_random_card(), draw_random_card()]))
def split(self, hand):
"""split the given hand"""
self.hands.remove(hand)
self.hands += hand.split()
class Hand:
def __init__(self, cards):
self.cards = cards
def hit(self):
"""add a random card"""
self.cards.append(draw_random_card())
def split(self):
"""split and return a pair of Hand objects"""
return [Hand(self.cards[0], draw_random_card()),
Hand(self.cards[1], draw_random_card())]
Isn't that simpler?
In response to your comment:
You can refer to any specific hand as self.hands[0] or self.hands[1] within the Players class.
If you want to keep track of a particular hand, you can just pass the hand itself around instead of passing a character string referring to that hand. Like this:
def process_hand(hand):
"""do something to a hand of cards"""
h.hit()
print h.cards()
h.hit()
h = Hand(cards)
process_hand(h)
This is important: modifications you make to the hand in the function work on the actual hand itself. Why put the extra step of passing a string that you then have to look up?
Also note that information specific to each hand, such as the bet, should probably be stored in the Hand class itself.
If you are sure you want to refer to each hand with a specific name (and again, it's not necessary in this case), you just use a dictionary with those names as keys:
self.hands = {}
self.hands["hand1"] = Hand([card1, card2])
self.hands["hand2"] = Hand([card1, card2])
print self.hands["hand1"]
But again, there is probably no good reason to do this. (And note that this is very different than instantiating a new variable "dynamically". It would be a good idea to look into how dictionaries work).