Python Variable Scope and Classes - python

In Python, if I define a variable:
my_var = (1,2,3)
and try to access it in __init__ function of a class:
class MyClass:
def __init__(self):
print my_var
I can access it and print my_var without stating (global my_var).
If I put my_var right after class MyClass however, I get scope error (no global variable found).
What is the reason for this? How should I do this? Where can I read about this to learn? I did read Python Class page but I did not encounter its explanation.
Thank you

Complementing #mgilson's answer:
Note that Python Class variables are shared among the class instances. And the behaviour might be VERY unexpected and seem weird. In practice it works like this:
class MyClass(object):
my_var = 10
def __init__(self):
print(self.my_var)
m1 = MyClass()
print(m1.my_var)
>>> 10 # this is fine
MyClass.my_var = 20
print(m1.my_var)
>>> 20 # WTF? :) --> shared value
m2 = MyClass()
print(m2.my_var)
>>> 20 # this is expected
m1.my_var = 30
print(MyClass.my_var)
>>> 20 # this is also expected
MyClass.my_var = 40
print(m1.my_var)
>>> 30 # But WHY? Isn't it shared? -->
# The value WAS shared until m1.my_var = 30 has happened.
print(m2.my_var)
>>> 40 # yep m2.my_var's value is still shared :)

When you put it right after class MyClass, it becomes a class attribute and you can get access to it via MyClass.my_var or as self.my_var from within the class (provided you don't create an instance variable with the same name).
Here's a little demo:
my_var = 'global'
class MyClass(object):
my_var = 'class'
def __init__(self):
print my_var #global
print MyClass.my_var #class
print self.my_var #class -- Only since we haven't set this attribute on the instance
self.my_var = 'instance' #set instance attribute.
print self.my_var #instance
print MyClass.my_var #class

If you write:
class MyClass(object):
my_var = (1, 2, 3)
you are defining an attribute of MyClass, not a variable. In your __init__ method, then, it is MyClass.my_var.

Once it's inside the class definition, it's no longer global, it's now in the class object's namespace. You can access it with self.my_var within __init__ though...

Related

Simple Question : about python class parenthesis

Look at the code below.
class A :
def __init__(self, a = "Hello") :
self.a = a
print(A().a) # 1
print(A.a) # 2
1 is not error
2 is error - AttributeError: type object 'A' has no attribute 'a'
What is the difference between the two results?
In your code A refers the the type of a class and also to its constructor/initialiser. A is called the class and when you construct an object of type A with the constructor you get an instance of that class.
A # Refers to the class A
A() # is an instance of class A
There is a difference between a class property and an instance property. Consider the following code:
class A:
propertyA = "hello"
def __init__(self, string="world"):
self.propertyB = string
In this snippet propertyA is a class property while propertyB is an instance property. Each instance of type A has its own propertyB and you must instantiate and object (an instance) first.
A.propertyA # Class property, does not need an instance
A().propertyB # instance property, needs an instance
In your code the constructor for A is the code written in the __init__. This code will be called when you type A(). Note that you specified a default value for the parameter a but if you don't you would call the constructor like this:
A("hello") # or:
A(a="hello")
Note that classes, instances and constructors are fundamentals of OOP (and by extension Python), you really should learn this, it avoids lots of basic errors.
You need to create an instance of the class first:
class A :
def __init__(self, a = "Hello") :
self.a = a
class_instance = A()
print(class_instance.a)
You can set the value of "a" when creating the instance by typing in the parenthesis:
class_instance = A("this is the value of a")
you can change the value after the creation like so:
class_instance.a = "New value of a"
A().a is creating an instance and returns the a value of the instance.
A.a cannot be executed because A is the Class name and doesn't have any attributes if you don't create an instance first

Instance variables and functions

I made a post here functions and class attributes (python)
When you define
a class attribute and gave it a function like this:
example 1
def add_age(cls,age):
cls.yrs_old = age
return cls
class Test:
age = add_age
a = Test()
a.age(5)
print(a.yrs_old)
self is automatically passed as the first argument of the add_age function.
However toying around with it more doing the same thing
but this time passing the function as an instance attribute like this:
example 2
def test_func(self):
self.class_attribute = "test"
class Test:
def __init__(self,func):
self.func = func
a = Test(test_func)
print(a.func())
Answers in the linked post stated that all functions in the class are automatically passed a self if the class is instantiated like this:
a = Test(test_func)
Now what's strange here is had I put test_func in a class attribute it works just like my very first example.
If you pass the function in the constructor/init like this:
def test_func(self):
self.class_attribute = "test"
class Test:
def __init__(self,func):
self.func = func
and call it like this:
a = Test(test_func)
print(a.func())
a.func is suddenly acting like a static method as opposed to example 1 where the function defined inside the class attribute becomes a regular class method.
What's going on?.
I thought all functions within a class are implicitly passed a self argument.
After the body of the class statement is evaluated, the metaclass wraps each function in a descriptor which takes care of the distinction between instance, class, and static methods. When you assign the function to an instance attribute, you bypass that machinery, so that the attribute refers to a plain function object.
From documentation -
Any function object that is a class attribute defines a method for instances of that class. It is not necessary that the function definition is textually enclosed in the class definition: assigning a function object to a local variable in the class is also ok.
This means that only methods that are assigned to classes are instance methods for the instances of the class.
Example -
>>> class A:
... def a(self):
... print("Hmm")
...
>>> b = A()
>>> b.a()
Hmm
>>> b.a
<bound method A.a of <__main__.A object at 0x006D13D0>>
But as soon as you assign a separate function object to the instance variable, it is no longer an instance method , since is is not defined at the class level, it is only defined for that particular instance , Example -
>>> def c():
... print("Hello")
...
>>> b.a = c
>>> b.a()
Hello
>>> b.a
<function c at 0x0017B198>
As you can see, when you directly assigned the function to the instance variable (instead of assigning it to class variable , it is now a normal instance attribute, that references a function object, and not an instance method.
You can also assign functions to class variables after the definition of class , and the instances would automatically get them as instance methods, Example -
>>> class A:
... def a(self):
... print("Hmm")
...
>>> def c(a):
... print("Hello - ", a)
...
>>> b = A()
>>> A.b = c
>>> b.b
<bound method A.c of <__main__.A object at 0x006D13D0>>
>>> b.b()
Hello <__main__.A object at 0x006D13D0>

What better way to get around the "static variables" in Python?

It seems that in Python, to declare a variable in a class, it is static (keeps its value in the next instances). What better way to get around this problem?
class Foo():
number = 0
def set(self):
self.number = 1
>>> foo = Foo()
>>> foo.number
0
>>> foo.set()
>>> foo.number
1
>>> new_foo = Foo()
>>> new_foo.number
1
Variables defined at the class level are indeed "static", but I don't think they work quite the way you think they do. There are 2 levels here which you need to worry about. There are attributes at the class level, and there are attributes at the instance level. Whenever you do self.attribute = ... inside a method, you're setting an attribute at the instance level. Whenever python looks up an attribute, it first looks at the instance level, if it doesn't find the attribute, it looks at the class level.
This can be a little confusing (especially if the attribute is a reference to a mutable object). consider:
class Foo(object):
attr = [] #class level attribute is Mutable
def __init__(self):
# in the next line, self.attr references the class level attribute since
# there is no instance level attribute (yet)
self.attr.append('Hello')
self.attr = []
# Now, we've created an instance level attribute, so further appends will
# append to the instance level attribute, not the class level attribute.
self.attr.append('World')
a = Foo()
print (a.attr) #['World']
print (Foo.attr) #['Hello']
b = Foo()
print (b.attr) #['World']
print (Foo.attr) #['Hello', 'Hello']
As others have mentioned, if you want an attribute to be specific to an instance, just initialize it as an instance attribute in __init__ (using self.attr = ...). __init__ is a special method which is run whenever a class is initialized (with a few exceptions that we won't discuss here).
e.g.
class Foo(object):
def __init__(self):
self.attr = 0
Just leave the declaration out. If you want to provide default values for the variables, initialize them in the __init__ method instead.
class Foo(object):
def __init__(self):
self.number = 0
def set(self):
self.number = 1
>>> foo = Foo()
>>> foo.number
0
>>> foo.set()
>>> foo.number
1
>>> new_foo = Foo()
>>> new_foo.number
0
Edit: replaced last line of the above snippet; it used to read 1 although it was just a typo on my side. Seems like it has caused quite a bit of confusion while I was away.
You maybe want to change the class attribute:
class Foo():
number = 0
def set(self):
Foo.number = 1
instead of overriding it!

In python. class variable is static..is I want to use different instance. is there any solution?

In python, I want to make a class variable static so I can to use it from a different instance. is there any solution?
I don't follow your question exactly, but it seems to me you're asking how to make instance variables in Python. The answer is to set them inside a method, preferably __init__() using the self reference.
class Foo(object):
classVar = 0 #this is a class variable. It is shared between all instances
def __init__(self, instanceVar):
self.someVar = instanceVar
obj1 = Foo(10)
obj2 = Foo(42)
print obj1.classVar # prints 0
print obj2.classVar # prints 0
print obj1.someVar #prints 10
print obj2.someVar #prints 42

Is it safe to replace a self object by another object of the same type in a method?

I would like to replace an object instance by another instance inside a method like this:
class A:
def method1(self):
self = func(self)
The object is retrieved from a database.
It is unlikely that replacing the 'self' variable will accomplish whatever you're trying to do, that couldn't just be accomplished by storing the result of func(self) in a different variable. 'self' is effectively a local variable only defined for the duration of the method call, used to pass in the instance of the class which is being operated upon. Replacing self will not actually replace references to the original instance of the class held by other objects, nor will it create a lasting reference to the new instance which was assigned to it.
As far as I understand, If you are trying to replace the current object with another object of same type (assuming func won't change the object type) from an member function. I think this will achieve that:
class A:
def method1(self):
newObj = func(self)
self.__dict__.update(newObj.__dict__)
It is not a direct answer to the question, but in the posts below there's a solution for what amirouche tried to do:
Python object conversion
Can I dynamically convert an instance of one class to another?
And here's working code sample (Python 3.2.5).
class Men:
def __init__(self, name):
self.name = name
def who_are_you(self):
print("I'm a men! My name is " + self.name)
def cast_to(self, sex, name):
self.__class__ = sex
self.name = name
def method_unique_to_men(self):
print('I made The Matrix')
class Women:
def __init__(self, name):
self.name = name
def who_are_you(self):
print("I'm a women! My name is " + self.name)
def cast_to(self, sex, name):
self.__class__ = sex
self.name = name
def method_unique_to_women(self):
print('I made Cloud Atlas')
men = Men('Larry')
men.who_are_you()
#>>> I'm a men! My name is Larry
men.method_unique_to_men()
#>>> I made The Matrix
men.cast_to(Women, 'Lana')
men.who_are_you()
#>>> I'm a women! My name is Lana
men.method_unique_to_women()
#>>> I made Cloud Atlas
Note the self.__class__ and not self.__class__.__name__. I.e. this technique not only replaces class name, but actually converts an instance of a class (at least both of them have same id()). Also, 1) I don't know whether it is "safe to replace a self object by another object of the same type in [an object own] method"; 2) it works with different types of objects, not only with ones that are of the same type; 3) it works not exactly like amirouche wanted: you can't init class like Class(args), only Class() (I'm not a pro and can't answer why it's like this).
Yes, all that will happen is that you won't be able to reference the current instance of your class A (unless you set another variable to self before you change it.) I wouldn't recommend it though, it makes for less readable code.
Note that you're only changing a variable, just like any other. Doing self = 123 is the same as doing abc = 123. self is only a reference to the current instance within the method. You can't change your instance by setting self.
What func(self) should do is to change the variables of your instance:
def func(obj):
obj.var_a = 123
obj.var_b = 'abc'
Then do this:
class A:
def method1(self):
func(self) # No need to assign self here
In many cases, a good way to achieve what you want is to call __init__ again. For example:
class MyList(list):
def trim(self,n):
self.__init__(self[:-n])
x = MyList([1,2,3,4])
x.trim(2)
assert type(x) == MyList
assert x == [1,2]
Note that this comes with a few assumptions such as the all that you want to change about the object being set in __init__. Also beware that this could cause problems with inheriting classes that redefine __init__ in an incompatible manner.
Yes, there is nothing wrong with this. Haters gonna hate. (Looking at you Pycharm with your in most cases imaginable, there's no point in such reassignment and it indicates an error).
A situation where you could do this is:
some_method(self, ...):
...
if(some_condition):
self = self.some_other_method()
...
return ...
Sure, you could start the method body by reassigning self to some other variable, but if you wouldn't normally do that with other parametres, why do it with self?
One can use the self assignment in a method, to change the class of instance to a derived class.
Of course one could assign it to a new object, but then the use of the new object ripples through the rest of code in the method. Reassiging it to self, leaves the rest of the method untouched.
class aclass:
def methodA(self):
...
if condition:
self = replace_by_derived(self)
# self is now referencing to an instance of a derived class
# with probably the same values for its data attributes
# all code here remains untouched
...
self.methodB() # calls the methodB of derivedclass is condition is True
...
def methodB(self):
# methodB of class aclass
...
class derivedclass(aclass):
def methodB(self):
#methodB of class derivedclass
...
But apart from such a special use case, I don't see any advantages to replace self.
You can make the instance a singleton element of the class
and mark the methods with #classmethod.
from enum import IntEnum
from collections import namedtuple
class kind(IntEnum):
circle = 1
square = 2
def attr(y): return [getattr(y, x) for x in 'k l b u r'.split()]
class Shape(namedtuple('Shape', 'k,l,b,u,r')):
self = None
#classmethod
def __repr__(cls):
return "<Shape({},{},{},{},{}) object at {}>".format(
*(attr(cls.self)+[id(cls.self)]))
#classmethod
def transform(cls, func):
cls.self = cls.self._replace(**func(cls.self))
Shape.self = Shape(k=1, l=2, b=3, u=4, r=5)
s = Shape.self
def nextkind(self):
return {'k': self.k+1}
print(repr(s)) # <Shape(1,2,3,4,5) object at 139766656561792>
s.transform(nextkind)
print(repr(s)) # <Shape(2,2,3,4,5) object at 139766656561888>

Categories

Resources