Understanding classes 'self' [duplicate] - python

This question already has answers here:
What is the purpose of the `self` parameter? Why is it needed?
(26 answers)
Closed 6 years ago.
In the following example employee is not used in the __init__ function, but we used it in the add_employee function calling self.employee.append().
Why is that? Why did we use self.employee.append() instead of employee.append() ? I thought we only use self for variables in the __init__ function.
class Workers():
employee = []
def __init__(self, name):
self.name = name
self.skills = []
self.add_employee()
def add_employee(self):
self.employee.append(self.name)
print('{} added to list'.format(self.name))

employee, __init__, and add_employee are just attributes of the class Workers.
employee is an attribute being a list, and __init__ is another attribute, being a method.
Also from the [def documentation](
https://docs.python.org/3/reference/compound_stmts.html#grammar-token-funcdef):
A function definition is an executable statement. Its execution binds the function name in the current local namespace to a function object (a wrapper around the executable code for the function).
so employees and __init__ and all other methods are really the same: names in a namespaces.
See also
https://docs.python.org/3/tutorial/classes.html#class-objects

The employee object is a class variable, not an instance variable. This means it is shared across all instances of that class. You can access it with classname.classvariablename or instancename.classvariablename. If you reassign an instance's version of it with something like instancename.classvariablename = newvalue, that instance will have a new instance variable of that name that masks its access to the class variable with the self reference (i.e., you won't be able to do instancename.classvariablename to get the class variable), but other instances - and the class - will still be able to (i.e., classname.classvariable will still work, and otherinstancename.classvariable will still point to that class variable). The following example demonstrates this.
>>> class A:
... l = []
...
>>> a = A()
>>> b = A()
>>> a.l
[]
>>> A.l
[]
>>> a.l = 3
>>> b.l
[]
>>> b.l.append(1)
>>> b.l
[1]
>>> A.l
[1]
>>> a.l
3

Related

How can I create classes with different class attributes within a function? [duplicate]

This question already has answers here:
Creating a class within a function and access a function defined in the containing function's scope [duplicate]
(2 answers)
Closed 2 years ago.
I would like to have a function that allows generating classes with custom class attributes. Like so:
def test_factory(a):
class Test:
a = a
return Test
However, when I try to call test_factory, I get an error:
test_factory(1)
> NameError: name 'a' is not defined
The expected behaviour would be:
t1 = test_factory(1)
t2 = test_factory(2)
print(t1.a, t2.a)
> 1, 2
How can I create classes that differ in their class attributes by calling a function?
You have to rename the function argument to not collide with the name of the class attribute:
def test_factory(b):
class Test:
a = b
return Test
>>> t1 = test_factory(1)
>>> t2 = test_factory(2)
>>> print(t1.a, t2.a)
1 2
When parsing the class statement, the assignment to a defines it as part of the temporary class namespace, similar to an assignment to a local variable in a function definition. As such, the name a then shadows the name of the parameter in the enclosing function scope.
You can either change the parameter name (as shown by schwobaseggl)
def test_factory(a_value):
class Test:
a = a_value
return Test
or set the attribute after the definition:
def test_factory(a):
class Test:
pass
Test.a = a
return Test
or call type directly:
def test_factory(a):
return type('Test', (), {'a': a})

Reference objects for accessing members [duplicate]

This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 5 years ago.
I would like to change the value of a member of an object. I can do this as long as i adress the object itself. However when I store a reference to this member it doesn't work. Instead it changes the reference object.
class MyClass:
def __init__(self, name):
self.a = 1
self.b = name
obj1=MyClass("string")
refob1=obj1.a
refob2=obj1.b
#I can change my object like this:
obj1.a=99
obj2.b="changed"
#But not like this:
refob1 = 1
refob2 = "changed_reference"
How can I use the reference object to change the members?
This is because int and strings in python are non-mutable objects. This means that they pass a copy of the value instead of the reference.
lists, in the other hand, are mutable objects and allow your desired behavior. Here you have an example:
class MyClass(object):
def __init__(self):
self.a = 3
self.b = [1]
obj = MyClass()
outside_a = obj.a
outside_a = 3 # outside_a is just a copy of obj.a value, so it won't change obj object
outisde_b = obj.b
outside_b[0] = 3 # Lists are mutabe, so this will change obj.b value
print obj.b # Prints [3]
So, how can you go around this? It is a bit ugly, but you can use a container (list) to contain the variables you want to reference later on. Here you can find a related post: Python passing an integer by reference

class initialization in Python

I found that some classes contain a __init__ function, and some don’t. I’m confused about something described below.
What is the difference between these two pieces of code:
class Test1(object):
i = 1
and
class Test2(object):
def __init__(self):
self.i = 1
I know that the result or any instance created by these two class and the way of getting their instance variable are pretty much the same. But is there any kind of “default” or “hidden” initialization mechanism of Python behind the scene when we don’t define the __init__ function for a class? And why I can’t write the first code in this way:
class Test1(object):
self.i = 1
That’s my questions. Thank you very much!
Thank you very much Antti Haapala! Your answer gives me further understanding of my questions. Now, I understand that they are different in a way that one is a "class variable", and the other is a "instance variable". But, as I tried it further, I got yet another confusing problem.
Here is what it is. I created 2 new classes for understanding what you said:
class Test3(object):
class_variable = [1]
def __init__(self):
self.instance_variable = [2]
class Test4(object):
class_variable = 1
def __init__(self):
self.instance_variable = 2
As you said in the answer to my first questions, I understand the class_variable is a "class variable" general to the class, and should be passed or changed by reference to the same location in the memory. And the instance_variable would be created distinctly for different instances.
But as I tried out, what you said is true for the Test3's instances, they all share the same memory. If I change it in one instance, its value changes wherever I call it.
But that's not true for instances of Test4. Shouldn't the int in the Test4 class also be changed by reference?
i1 = Test3()
i2 = Test3()
>>> i1.i.append(2)
>>> i2.i
[1, 2]
j1 = Test4()
j2 = Test4()
>>> j1.i = 3
>>> j2.i
1
Why is that? Does that "=" create an "instance variable" named "i" without changing the original "Test4.i" by default? Yet the "append" method just handles the "class variable"?
Again, thank you for your exhaustive explanation of the most boring basic concepts to a newbie of Python. I really appreciate that!
In python the instance attributes (such as self.i) are stored in the instance dictionary (i.__dict__). All the variable declarations in the class body are stored as attributes of the class.
Thus
class Test(object):
i = 1
is equivalent to
class Test(object):
pass
Test.i = 1
If no __init__ method is defined, the newly created instance usually starts with an empty instance dictionary, meaning that none of the properties are defined.
Now, when Python does the get attribute (as in print(instance.i) operation, it first looks for the attribute named i that is set on the instance). If that fails, the i attribute is looked up on type(i) instead (that is, the class attribute i).
So you can do things like:
class Test:
i = 1
t = Test()
print(t.i) # prints 1
t.i += 1
print(t.i) # prints 2
but what this actually does is:
>>> class Test(object):
... i = 1
...
>>> t = Test()
>>> t.__dict__
{}
>>> t.i += 1
>>> t.__dict__
{'i': 2}
There is no i attribute on the newly created t at all! Thus in t.i += 1 the .i was looked up in the Test class for reading, but the new value was set into the t.
If you use __init__:
>>> class Test2(object):
... def __init__(self):
... self.i = 1
...
>>> t2 = Test2()
>>> t2.__dict__
{'i': 1}
The newly created instance t2 will already have the attribute set.
Now in the case of immutable value such as int there is not that much difference. But suppose that you used a list:
class ClassHavingAList():
the_list = []
vs
class InstanceHavingAList()
def __init__(self):
self.the_list = []
Now, if you create 2 instances of both:
>>> c1 = ClassHavingAList()
>>> c2 = ClassHavingAList()
>>> i1 = InstanceHavingAList()
>>> i2 = InstanceHavingAList()
>>> c1.the_list is c2.the_list
True
>>> i1.the_list is i2.the_list
False
>>> c1.the_list.append(42)
>>> c2.the_list
[42]
c1.the_list and c2.the_list refer to the exactly same list object in memory, whereas i1.the_list and i2.the_list are distinct. Modifying the c1.the_list looks as if the c2.the_list also changes.
This is because the attribute itself is not set, it is just read. The c1.the_list.append(42) is identical in behaviour to
getattr(c1, 'the_list').append(42)
That is, it only tries read the value of attribute the_list on c1, and if not found there, then look it up in the superclass. The append does not change the attribute, it just changes the value that the attribute points to.
Now if you were to write an example that superficially looks the same:
c1.the_list += [ 42 ]
It would work identical to
original = getattr(c1, 'the_list')
new_value = original + [ 42 ]
setattr(c1, 'the_list', new_value)
And do a completely different thing: first of all the original + [ 42 ] would create a new list object. Then the attribute the_list would be created in c1, and set to point to this new list. That is, in case of instance.attribute, if the attribute is "read from", it can be looked up in the class (or superclass) if not set in the instance, but if it is written to, as in instance.attribute = something, it will always be set on the instance.
As for this:
class Test1(object):
self.i = 1
Such thing does not work in Python, because there is no self defined when the class body (that is all lines of code within the class) is executed - actually, the class is created only after all the code in the class body has been executed. The class body is just like any other piece of code, only the defs and variable assignments will create methods and attributes on the class instead of setting global variables.
I understood my newly added question. Thanks to Antti Haapala.
Now, when Python does the get attribute (as in print(instance.i) operation, it first looks for the attribute named i that is set on the instance). If that fails, the i attribute is looked up on type(i) instead (that is, the class attribute i).
I'm clear about why is:
j1 = Test4()
j2 = Test4()
>>> j1.i = 3
>>> j2.i
1
after few tests. The code
j1.3 = 3
actually creates a new instance variable for j1 without changing the class variable. That's the difference between "=" and methods like "append".
I'm a newbie of Python coming from c++. So, at the first glance, that's weird to me, since I never thought of creating a new instance variable which is not created in the class just using the "=". It's really a big difference between c++ and Python.
Now I got it, thank you all.

Understanding Python inheritance and initialisation [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument
In Python 2.7, consider I have the following code:
class Base(object):
# Variant 1
def __init__(self, records=[]):
self._records = records
# Variant 2
# def __init__(self, records=[]):
# self._records = []
# if records:
# self._records = records
def append(self, value):
self._records.append(value)
class ChildA(Base):
pass
class ChildB(Base):
pass
a = ChildA()
b = ChildB()
a.append(100)
b.append(200)
print a._records
print b._records
If I use variant 1 to initialize my base class, self._records behaves like a class variable. Executing the code using variant 1 to initialize my base class, I get the ouput:
[100, 200]
[100, 200]
Using variant 2 to initialize my base class, self._records behaves like a instance variable (as expected). Executing the code using variant 2 to initialize my base class, I get the output:
[100]
[200]
What is the difference between these both variants? Why does variant 1 work different to variant 2? Thanks a lot for your help!
Your default argument is [], which is a common pitfall with Python. See more in the tutorial:
Important warning: The default value is evaluated only once. This
makes a difference when the default is a mutable object such as a
list, dictionary, or instances of most classes.
It has nothing to do with inheritance, class or instance variables. Consider the next code:
>>> def f(a=[]):
... a.append(1)
... print a
...
>>> f.func_defaults
([],)
>>> f()
[1]
>>> f()
[1, 1]
>>> f.func_defaults
([1, 1],)
Default values for function parameters are evaluated only ones and stored within function object. Each time f is called it operates with the same list. Right as in your case.
As the others had put - this has to do with using an empty list as default value, not with class inheritance behavior at all.
The correct way to do it is:
class Base(object):
# Variant 1
def __init__(self, records=None):
if records is None:
records = []
self._records = records
Therefore ensuring a new list instance is created each time the Base class is instantiated. The way you put it in your code, a list object is instantiated when your class body is parsed by Python - and that one instance of list is used as the default parameter each time the __init__ method is run. As the objects hold a reference and change that same list, the change is visible in all other objects which share the Base class.

What happens when I instantiate class in Python?

Could you clarify some ideas behind Python classes and class instances?
Consider this:
class A():
name = 'A'
a = A()
a.name = 'B' # point 1 (instance of class A is used here)
print a.name
print A.name
prints:
B
A
if instead in point 1 I use class name, output is different:
A.name = 'B' # point 1 (updated, class A itself is used here)
prints:
B
B
Even if classes in Python were some kind of prototype for class instances, I'd expect already created instances to remain intact, i.e. output like this:
A
B
Can you explain what is actually going on?
First of all, the right way in Python to create fields of an instance (rather than class fields) is using the __init__ method. I trust that you know that already.
Python does not limit you in assigning values to non-declared fields of an object. For example, consider the following code:
class Empty: pass
e = Empty()
e.f = 5
print e.f # shows 5
So what's going in your code is:
You create the class A with a static field name assigned with A.
You create an instance of A, a.
You create a new field for the object a (but not for other instances of A) and assign B to it
You print the value of a.name, which is unique to the object a.
You print the value of the static field A.name, which belongs to the class
Perhaps this example may make things more help clarify. Recall that Python names are not storage (as variables are in other languages) but references to storage. You can find what a name refers to with id(name). The identity operator x is y tells whether two names point at the same object.
>>> class A(object):
... name = 'A'
...
>>> x = A()
>>> A.name is x.name
True
>>> x.name = 'fred' # x.name was bound to a new object (A.name wasn't)
>>> A.name is x.name
False
>>> x = A() # start over
>>> A.name is x.name
True # so far so good
>>> A.name = 'fred'
>>> A.name is x.name
True # this is somewhat counter-intuitive
You also should look at these SO threads for further explanations:
Static class variables in Python
In Python how can I access "static" class variables within class methods
And an official tutorial:
http://docs.python.org/tutorial/classes.html#SECTION0011320000000000000000
Keep in mind that the assignment "=" operator in python behaves differently than C++ or Java:
http://docs.python.org/reference/simple_stmts.html#assignment-statements

Categories