Python Class scope & lists - python

I'm still fairly new to Python, and my OO experience comes from Java. So I have some code I've written in Python that's acting very unusual to me, given the following code:
class MyClass():
mylist = []
mynum = 0
def __init__(self):
# populate list with some value.
self.mylist.append("Hey!")
# increment mynum.
self.mynum += 1
a = MyClass()
print a.mylist
print a.mynum
b = MyClass()
print b.mylist
print b.mynum
Running this results in the following output:
['Hey!']
1
['Hey!', 'Hey!']
1
Clearly, I would expect the class variables to result in the same exact data, and the same exact output... What I can't seem to find anywhere is what makes a list different than say a string or number, why is the list referencing the same list from the first instantiation in subsequent ones? Clearly I'm probably misunderstanding some kind of scope mechanics or list creation mechanics..

tlayton's answer is part of the story, but it doesn't explain everything.
Add a
print MyClass.mynum
to become even more confused :). It will print '0'. Why? Because the line
self.mynum += 1
creates an instance variable and subsequently increases it. It doesn't increase the class variable.
The story of the mylist is different.
self.mylist.append("Hey!")
will not create a list. It expects a variable with an 'append' function to exist. Since the instance doesn't have such a variable, it ends up referring the one from the class, which does exist, since you initialized it. Just like in Java, an instance can 'implicitly' reference a class variable. A warning like 'Class fields should be referenced by the class, not by an instance' (or something like that; it's been a while since I saw it in Java) would be in order. Add a line
print MyClass.mylist
to verify this answer :).
In short: you are initializing class variables and updating instance variables. Instances can reference class variables, but some 'update' statements will automagically create the instance variables for you.

I believe the difference is that += is an assignment (just the same as = and +), while append changes an object in-place.
mylist = []
mynum = 0
This assigns some class variables, once, at class definition time.
self.mylist.append("Hey!")
This changes the value MyClass.mylist by appending a string.
self.mynum += 1
This is the same as self.mynum = self.mynum + 1, i.e., it assigns self.mynum (instance member). Reading from self.mynum falls through to the class member since at that time there is no instance member by that name.

What you are doing here is not just creating a class variable. In Python, variables defined in the class body result in both a class variable ("MyClass.mylist") and in an instance variable ("a.mylist"). These are separate variables, not just different names for a single variable.
However, when a variable is initialized in this way, the initial value is only evaluated once and passed around to each instance's variables. This means that, in your code, the mylist variable of each instance of MyClass are referring to a single list object.
The difference between a list and a number in this case is that, like in Java, primitive values such as numbers are copied when passed from one variable to another. This results in the behavior you see; even though the variable initialization is only evaluated once, the 0 is copied when it is passed to each instance's variable. As an object, though, the list does no such thing, so your append() calls are all coming from the same list. Try this instead:
class MyClass():
def __init__(self):
self.mylist = ["Hey"]
self.mynum = 1
This will cause the value to be evaluated separately each time an instance is created. Very much unlike Java, you don't need the class-body declarations to accompany this snippet; the assignments in the __init__() serve as all the declaration that is needed.

Related

How to add user class objects to a list of objects

I'm trying to append objects to a list of objects, but all atrributes of the object's class are the same as the last entry
I found out that declaring the object in my for loop, fixes the problem, but don't understand why...
(It seems like all the objects in the list, reference to the same object)
First I declare:
class SimpleClass:
name = ""
simplelist = []
this works:
for count in range(4):
x = SimpleClass()
x.name = count
simplelist.append(x)
this doesn't work (all values in "simplelist[].name" equal 3) - WHY?
x = SimpleClass()
for count in range(4):
x.name = count
simplelist.append(x)
x = SimpleClass() creates the instance of class (something like an object of this type).
If you run it outside the loop, you have exactly one object and try to change it. simplelist.append(x) in this case will append this unique object to list (and will change it each time) so you will have a list which contains many copies of one object.
If you run it inside the loop, you have several various object instantiating one class. simplelist.append(x) in this case will append each new object to list so you will have a list which contains many different objects.
From the official Python docs:
Class instantiation uses function notation. Just pretend that the class object is a parameterless function that returns a new instance of the class. For example (assuming the above class):
x = MyClass()
creates a new instance of the class and assigns this object to the local variable x.
The instantiation operation (“calling” a class object) creates an empty object. Many classes like to create objects with instances customized to a specific initial state.
In the second case you create just one object of the class. Then you alter this object and by this all already existing copies of the object in the list.
This is because you are creating only one object and you add it 4 times to to your list. All the objects (thus 4 times the same) will have the same value as name and this will be 3 since it is the last modification you did in the loop.
In yourcase the x is an instance of SimpleClass class. Actually it is a reference/pointer and it is not changed in your for loop. You only change the value of name attribute of SimpleClass class. So your list will contain the same reference 3 times.

How to get the name of an object from a class in Python? [duplicate]

This question already has answers here:
How to convert variable into string in python
(9 answers)
Getting an instance name inside class __init__() [duplicate]
(10 answers)
Closed 6 years ago.
I know this is a weird idea. The idea would be something like this:
class AnyClass:
def __init__(self):
# print object name
Then create a new object
test = AnyClass()
And finally get this output:
'test'
That is not the idea behind this, but is an easy example of what I'm trying to...
PS: I'm not trying to get the class name, just the object name (if possible)
PS2: I know I can get the name with test.__name__ but I'm trying to get the name inside the class, not outside.
Consider this:
>>> a = dict()
>>> b = a
Both a and b reference the exact same object.
>>> a is b
True
When you do a . operation on an object, you're looking up an attribute on that object. An object can be referenced in many different locations; it makes no sense for it to store all those reference names, especially when those names are only bound within certain contexts. For example
def generator():
a = dict()
yield a
b = next(generator())
Both a and b refer to the same dict object, but you can't use a to reference the dict anywhere else besides in the generator function.
Within a specific context, you can test the bound names and see if they refer to a specific object.
test = MyObject()
for name, obj in locals().items():
if test is obj:
print name
First: you don't want to do this, there is no reason to do this, and if you think you need to do this, you're wrong.
Second: you can't do it in the __init__ method because the name reference test referring to the new AnyClass instance object hasn't been added to the memory space ("bound") yet. However, you could do it like this.
class AnyClass():
def echo_name(self):
{v:k for k,v in locals().items()}[self]
test = AnyClass()
test.echo_name()
This will return the first variable encountered in the locals() dictionary that is assigned to the test object. There is no guarantee for the order in which those variables will be returned.
To explain a bit further about why it won't work in the __init__ method, when you do this:
test = AnyClass()
A new instance of AnyClassis constructed according to the instructions of the class definition (including the definitions of any parent or metaclass). This construction happens in phases, the last phase of which is executing the __init__ method. Prior to __init__, other methods that will be executed, if they exist, are __new__, and also the the __new__, __init__, and __call__ methods of the metaclass (if one exists).
So at the point in time the code in the body of the __init__ method is being executed, the object is still being constructed. Therefore there is, as of yet, nothing in the locals() dictionary assigned to the name 'test'. There is only a member called 'self'. And, obviously, if you reverse-lookup the self object in the locals() dictionary looking for a registered name, the name you will get is the name 'self'. Which... isn't useful.

Creating a global variable (from a string) from within a class

Context: I'm making a Ren'py game. The value is Character(). Yes, I know this is a dumb idea outside of this context.
I need to create a variable from an input string inside of a class that exists outside of the class' scope:
class Test:
def __init__(self):
self.dict = {} # used elsewhere to give the inputs for the function below.
def create_global_var(self, variable, value):
# the equivalent of exec("global {0}; {0} = {1}".format(str(variable), str(value)))
# other functions in the class that require this.
Test().create_global_var("abc", "123") # hence abc = 123
I have tried vars()[], globals()[variable] = value, etc, and they simply do not work (they don't even define anything) Edit: this was my problem.
I know that the following would work equally as well, but I want the variables in the correct scope:
setattr(self.__class__, variable, value) # d.abc = 123, now. but incorrect scope.
How can I create a variable in the global scope from within a class, using a string as the variable name, without using attributes or exec in python?
And yes, i'll be sanity checking.
First things first: what we call the "global" scope in Python is actually the "module" scope
(on the good side, it diminishes the "evils" of using global vars).
Then, for creating a global var dynamically, although I still can't see why that would
be better than using a module-level dictionary, just do:
globals()[variable] = value
This creates a variable in the current module. If you need to create a module variable on the module from which the method was called, you can peek at the globals dictionary from the caller frame using:
from inspect import currentframe
currentframe(1).f_globals[variable] = name
Now, the this seems especially useless since you may create a variable with a dynamic name, but you can't access it dynamically (unless using the globals dictionary again)
Even in your test example, you create the "abc" variable passing the method a string, but then you have to access it by using a hardcoded "abc" - the language itself is designed to discourage this (hence the difference to Javascript, where array indexes and object attributes are interchangeable, while in Python you have distinct Mapping objects)
My suggestion is that you use a module-level explicit dictionary and create all your
dynamic variables as key/value pairs there:
names = {}
class Test(object):
def __init__(self):
self.dict = {} # used elsewhere to give the inputs for the function below.
def create_global_var(self, variable, value):
names[variable] = value
(on a side note, in Python 2 always inherit your classes from "object")
You can use setattr(__builtins__, 'abc', '123') for this.
Do mind you that this is most likely a design problem and you should rethink the design.

Python Variable Declaration

I want to clarify how variables are declared in Python.
I have seen variable declaration as
class writer:
path = ""
sometimes, there is no explicit declaration but just initialization using __init__:
def __init__(self, name):
self.name = name
I understand the purpose of __init__, but is it advisable to declare variable in any other functions?
How can I create a variable to hold a custom type?
class writer:
path = "" # string value
customObj = ??
Okay, first things first.
There is no such thing as "variable declaration" or "variable initialization" in Python.
There is simply what we call "assignment", but should probably just call "naming".
Assignment means "this name on the left-hand side now refers to the result of evaluating the right-hand side, regardless of what it referred to before (if anything)".
foo = 'bar' # the name 'foo' is now a name for the string 'bar'
foo = 2 * 3 # the name 'foo' stops being a name for the string 'bar',
# and starts being a name for the integer 6, resulting from the multiplication
As such, Python's names (a better term than "variables", arguably) don't have associated types; the values do. You can re-apply the same name to anything regardless of its type, but the thing still has behaviour that's dependent upon its type. The name is simply a way to refer to the value (object). This answers your second question: You don't create variables to hold a custom type. You don't create variables to hold any particular type. You don't "create" variables at all. You give names to objects.
Second point: Python follows a very simple rule when it comes to classes, that is actually much more consistent than what languages like Java, C++ and C# do: everything declared inside the class block is part of the class. So, functions (def) written here are methods, i.e. part of the class object (not stored on a per-instance basis), just like in Java, C++ and C#; but other names here are also part of the class. Again, the names are just names, and they don't have associated types, and functions are objects too in Python. Thus:
class Example:
data = 42
def method(self): pass
Classes are objects too, in Python.
So now we have created an object named Example, which represents the class of all things that are Examples. This object has two user-supplied attributes (In C++, "members"; in C#, "fields or properties or methods"; in Java, "fields or methods"). One of them is named data, and it stores the integer value 42. The other is named method, and it stores a function object. (There are several more attributes that Python adds automatically.)
These attributes still aren't really part of the object, though. Fundamentally, an object is just a bundle of more names (the attribute names), until you get down to things that can't be divided up any more. Thus, values can be shared between different instances of a class, or even between objects of different classes, if you deliberately set that up.
Let's create an instance:
x = Example()
Now we have a separate object named x, which is an instance of Example. The data and method are not actually part of the object, but we can still look them up via x because of some magic that Python does behind the scenes. When we look up method, in particular, we will instead get a "bound method" (when we call it, x gets passed automatically as the self parameter, which cannot happen if we look up Example.method directly).
What happens when we try to use x.data?
When we examine it, it's looked up in the object first. If it's not found in the object, Python looks in the class.
However, when we assign to x.data, Python will create an attribute on the object. It will not replace the class' attribute.
This allows us to do object initialization. Python will automatically call the class' __init__ method on new instances when they are created, if present. In this method, we can simply assign to attributes to set initial values for that attribute on each object:
class Example:
name = "Ignored"
def __init__(self, name):
self.name = name
# rest as before
Now we must specify a name when we create an Example, and each instance has its own name. Python will ignore the class attribute Example.name whenever we look up the .name of an instance, because the instance's attribute will be found first.
One last caveat: modification (mutation) and assignment are different things!
In Python, strings are immutable. They cannot be modified. When you do:
a = 'hi '
b = a
a += 'mom'
You do not change the original 'hi ' string. That is impossible in Python. Instead, you create a new string 'hi mom', and cause a to stop being a name for 'hi ', and start being a name for 'hi mom' instead. We made b a name for 'hi ' as well, and after re-applying the a name, b is still a name for 'hi ', because 'hi ' still exists and has not been changed.
But lists can be changed:
a = [1, 2, 3]
b = a
a += [4]
Now b is [1, 2, 3, 4] as well, because we made b a name for the same thing that a named, and then we changed that thing. We did not create a new list for a to name, because Python simply treats += differently for lists.
This matters for objects because if you had a list as a class attribute, and used an instance to modify the list, then the change would be "seen" in all other instances. This is because (a) the data is actually part of the class object, and not any instance object; (b) because you were modifying the list and not doing a simple assignment, you did not create a new instance attribute hiding the class attribute.
This might be 6 years late, but in Python 3.5 and above, you can give a hint about a variable type like this:
variable_name: type_name
or this:
variable_name # type: shinyType
This hint has no effect in the core Python interpreter, but many tools will use it to aid the programmer in writing correct code.
So in your case(if you have a CustomObject class defined), you can do:
customObj: CustomObject
See this or that for more info.
There's no need to declare new variables in Python. If we're talking about variables in functions or modules, no declaration is needed. Just assign a value to a name where you need it: mymagic = "Magic". Variables in Python can hold values of any type, and you can't restrict that.
Your question specifically asks about classes, objects and instance variables though. The idiomatic way to create instance variables is in the __init__ method and nowhere else — while you could create new instance variables in other methods, or even in unrelated code, it's just a bad idea. It'll make your code hard to reason about or to maintain.
So for example:
class Thing(object):
def __init__(self, magic):
self.magic = magic
Easy. Now instances of this class have a magic attribute:
thingo = Thing("More magic")
# thingo.magic is now "More magic"
Creating variables in the namespace of the class itself leads to different behaviour altogether. It is functionally different, and you should only do it if you have a specific reason to. For example:
class Thing(object):
magic = "Magic"
def __init__(self):
pass
Now try:
thingo = Thing()
Thing.magic = 1
# thingo.magic is now 1
Or:
class Thing(object):
magic = ["More", "magic"]
def __init__(self):
pass
thing1 = Thing()
thing2 = Thing()
thing1.magic.append("here")
# thing1.magic AND thing2.magic is now ["More", "magic", "here"]
This is because the namespace of the class itself is different to the namespace of the objects created from it. I'll leave it to you to research that a bit more.
The take-home message is that idiomatic Python is to (a) initialise object attributes in your __init__ method, and (b) document the behaviour of your class as needed. You don't need to go to the trouble of full-blown Sphinx-level documentation for everything you ever write, but at least some comments about whatever details you or someone else might need to pick it up.
For scoping purpose, I use:
custom_object = None
Variables have scope, so yes it is appropriate to have variables that are specific to your function. You don't always have to be explicit about their definition; usually you can just use them. Only if you want to do something specific to the type of the variable, like append for a list, do you need to define them before you start using them. Typical example of this.
list = []
for i in stuff:
list.append(i)
By the way, this is not really a good way to setup the list. It would be better to say:
list = [i for i in stuff] # list comprehension
...but I digress.
Your other question.
The custom object should be a class itself.
class CustomObject(): # always capitalize the class name...this is not syntax, just style.
pass
customObj = CustomObject()
As of Python 3, you can explicitly declare variables by type.
For instance, to declare an integer one can do it as follows:
x: int = 3
or:
def f(x: int):
return x
see this question for more detailed info about it:
Explicitly declaring a variable type in Python

Class variable and instance variable question in Python

When I have this class, the variable 'value' is class variable.
class Hello:
value = 10
def __init__(self):
print 'init'
I have an object 'h' and I could get the same value of '10' both for Hello.value and h.value.
h = Hello()
print Hello.value
print h.value
When I run this command,
h.value = 20
I get value '10', and '20', when I run them.
print Hello.value
print h.value
Why is this?
Q1 : Why 'print h.value' prints out the value of Hello.value, not raise an error?
Q2 : Does h.value = 20 introduce a new variable similar to 'self.value = 20'?
Q3 : Is there a way to prevent creating an instance variable (or prevent running code 'h.value = 20')?
That's how attribute lookup works in Python:
If an attribute can't be found in the dictionary of an instance, it is looked up in the dictionary of its class, and if it can't be found there, also in the dictionaries of the base classes.
If you assign to an instance attribute, this will always only affect the instance -- i.e. update the attribute if it already exists in the instance or create it in the instance's dictionary if not.
If you don't like this behaviour, you can overwrite the __setattr__() method to do whatever you like -- for example throwing an error if you don't want to allow the creation of instance attributes. The latter can also be achieved by adding __slots__ = [] to the class.
If Python looks up o.attr, it first checks the object instance, then its class, then the base class, and so on. It does so both for methods and data attributes (i.e. there is no distinction between data and code attributes).
On assignment, the value always gets assigned to the instance. So
A1. Because it falls back to the
class (which it must, because methods
wouldn't work otherwise)
A2. Yes, it does.
A3. You can define a __setattr__
method that raises an exception.

Categories