I have one small question regarding Python classes and variables.
Why 'self' keyword 'spoils' the work of counter variable in the first example while in 2nd example (which is pretty similar in terms of mechanism to the first one) 'self' keyword does not do anything 'wrong' in terms of the outcome:
# Counter does not work properly (incrementation) if 'self' keyword is provided before the variable named 'counter'.
# Instead of 'self' keyword we shall use class name, to make this work 'ExampleOne.counter += 1'.
class ExampleOne:
counter = 0
def __init__(self, name, surname):
self.name = name
self.surname = surname
self.counter += 1
# It works, meaning list is being updated. Even if 'self' keyword is provided before variable named 'list'.
class ExampleTwo:
list = []
def __init__(self, name, surname):
self.name = name
self.surname = surname
self.list.append([self.name, self.surname])
self.counter, when looking up a value, will check the class after the instance; but when assigning a value, will just assign to the instance (because it tries the instance first and will succeed - there's nothing to prevent it from working).
self.counter += 1 is equivalent to self.counter = self.counter + 1; the newly computed value, based on the class attribute, is assigned as an instance attribute.
self.list.append(...) calls a method on the looked-up list object; that object's identity never changes and there is no assignment. So each access to self.list finds the class attribute, since there are never any instance attributes to be found first.
For more technical details, you can see e.g. How python attribute lookup process works? .
You might wonder why the lookup is allowed to find class attributes via the instance in the first place. The reason is that it is sometimes useful for polymorphism; depending on the subtype of the instance, a class attribute could be found in a different class.
Related
List item
class Car:
def __init__(self, color, brand, number_of_seats):
self.color = color
self.brand = brand
self.number_of_seats = number_of_seats
self.number_of_wheels = 4
self.registration_number = GenerateRegistrationNumber()
Hi all,
1)Referring to the above example, could anyone tell me what is the difference between specific attributed and "the other" attributes? What will happen if registration_number is treated as a specific attribute?
2)
class MyInteger:
def __init__(self, newvalue):
# imagine self as an index card.
# under the heading of "value", we will write
# the contents of the variable newvalue.
self.value = newvalue
If we consider this example, shouldn't it be self.newvalue = newvalue?
I think I know what you're asking (let me know if I'm wrong), but I think you're asking what the difference is between the attributes that are assigned by the parameters of __init__ (Instance Attributes), ones that are assigned inside the __init__ method but not with parameters (also Instance Attributes), and ones that are not assigned in the initialiser at all (Class Attributes). The difference here is that all (well, pretty much all) cars have 4 wheels, and the number plate is generated, not supplied. You could also do this, for example:
class Car:
number_of_wheels = 4
def __init__(self, color, brand, number_of_seats):
self.color = color
self.brand = brand
self.number_of_seats = number_of_seats
self.registration_number = GenerateRegistrationNumber()
As the number of wheels here is always assigned to the same value, across all instances, it is said to be a "Class Attribute" in this case. All other attributes here are “Instance Attributes” as they are specifically assigned to each instance. For a slightly better explanation, I recommend reading this:
https://www.geeksforgeeks.org/class-instance-attributes-python/
It doesn't actually matter what the instance attribute (self.value here) is called, you could call it whatever you want and it'd still work, but in most cases, you would indeed want to name the attribute the same as the parameter.
init function also called as magic function is a constructor function for a class. if you remember in java whenever we make a class the constructor method should have the same name as the classname but this is not the scenario in python . In python we make use of the init method
the difference between the class attributes and instance attributes is that the class attributes are common to every object that is created but the instance attributes are only accessible by the object that is created.
consider a example where data of students in a class is stored. In such case the class division will be same for all the students of that particular class so it can be common but names of all students are different and also their marks and so on and hence they should be different for everyone
in previous scenario the class division can be class attribute and the data of student like name , marks has to be instance attributes
examples of class attribute is as shown
class A:
Division = 5A
here the division is a class attribute
class B:
def __init__(self,name,marks):
self.name = name
self.marks = marks
here the name and marks are instance variables
now here we can also write self.username = name because we are storing the value of name variable in self.username so you can write any name there is no constraint on that
Also whenever you write __ in front of method or variable it means that the attribute is private and accessible by only class.
Is it bad practice to access a class variable through the self keyword?
Doing so suites my needs but I wonder if it's considered blasphemous (or, even, un-pythonic).
Example:
class A:
_class_specific = 1
def __init__(self, i):
self.instance_specific = i
def get_special_sum(self):
return self._class_specific + self.instance_specific
t = A(2)
t.get_special_sum() #< Returns 3
Reason for doing so:
I have a class variable _ID and an instance method check(self) defined in a Base class. Child classes have their own values for _ID. The inherited check(self) method must have access to instance-specific variables as well as the _ID variable of a given genus, which it will access through self._ID
Although the above works perfectly I wonder if there is a more explicit way of doing it? ...other than leaving a glaringly obvious comment making clear what the intention is...
Since I get variable definitions from an external text file i need to modify my local variables indirectly.
What I want to do is basically working, but not once I try to implement it within a class.
X = "0"
vars()["X"]+="1"
print(X) #gives "01" as expected
class Test:
def __init__(self):
x = "0"
vars()["x"]+="1"
self.x = x
test = Test()
print(test.x) # gives "0", but why?
While the procedual code snip produces the expected result "01", the code inside the class does not ("0"). Why?
Here is what the manuals says about vars (https://docs.python.org/3/library/functions.html#vars):
Return the dict attribute for a module, class, instance, or any other object with a dict attribute.
Objects such as modules and instances have an updateable dict attribute; however, other objects may have write restrictions on their dict attributes (for example, classes use a types.MappingProxyType to prevent direct dictionary updates).
Without an argument, vars() acts like locals(). Note, the locals dictionary is only useful for reads since updates to the locals dictionary are ignored.
In other words, when writing directly you are at the module level that has a writable __dict__, not so when you are inside a method.
Although this is bad practice and is not recommended, but this can help you get going:
class Test:
def __init__(self):
self.x = '0'
self.__dict__['x']+='1'
test = Test()
print(test.x) # gives "01"
You are mixing a lot of things here. First I will comment on your code:
X = "0"
vars()["X"]+="1"
print(X) #gives "01" as expected
class Test:
def __init__(self):
global X
x = "0" # this is a local variable inside __init__()
vars()["X"]+="1" # vars() only shows local variables, will lead to an key error because vars()['X'] does not exist
self.x = x # assign member variable with value of local variable
test = Test()
print(test.x) # gives "0", because you just assigned self.x = x, which is "0"
You could use the global keyword to make X visible inside init() but this is considered bad practice.
X = "0"
class Test(object):
def __init__(self):
global X
self.x = X + "1"
test = Test()
print(test.x) # gives "01"
You better initalize Test with the variable needed:
X = "0"
class Test(object):
def __init__(self, myvar: str):
self.x = myvar + "1"
test = Test(X)
print(test.x) # gives "01"
The documenation for vars() without an argument says:
Without an argument, vars() acts like locals(). Note, the locals dictionary is only useful for reads since updates to the locals dictionary are ignored.
But that is incomplete. The documentation for locals() says:
Note that at the module level, locals() and globals() are the same dictionary.
And changes to the dictionary returned by globals() are not ignored as it is the actual symbol table:
Return a dictionary representing the current global symbol table. This is always the dictionary of the current module (inside a function or method, this is the module where it is defined, not the module from which it is called).
So by this really strange design, vars() when used at module level actually does return the writeable symbol table -- and anywhere else, it uses a view on which writes are ignored.
None of this really matters, as using these functions is usually a good sign that you're doing something wrong, but still, here it is.
Will class variable ever reset on its own when instance for that particular class is still present?
I have a class and during instantiating an object, I update class variable within init for future use where I would not have access to the instantiated object. I know for a fact that the object is no out of scope when I try to access this class variable. Sample snippet is given below.
Class A:
var = ""
def __init__(self,name):
self.name = name
A.var = name
A_obj = A("John")
I want to use var (which is "John") at a later part. when I get to that part, value of "A.var" is "" and not "John" as I expected The complete code is complicated to be posted here. So I have just provided basic scenario of what is happening
No.
Rather than a working example which would let us reproduce the symptom you see, you chose to provide code which works as documented and never shows the symptom, leaving us to guess about your situation. I enclose my guess, a slightly longer version of your code:
def empty():
print('empty')
return ''
class A:
var = empty()
def __init__(self, name):
self.name = name
A.var = name
obj_john = A('John')
print(A.var)
obj_mary = A('Mary')
print(A.var)
The big difference is logging of the empty string assignment, which seems to be your chief concern. Unsurprisingly, the output produced is:
empty
John
Mary
That is, the empty string was assigned exactly once, and then the ctor repeatedly overwrote the singleton.
If you abuse repeated imports of your module then you might manage to invoke the empty assignment twice, but you haven't described any of those interactions. Setting a debugger watchpoint might prove useful. Dig a bit further, and share with us what you found.
How do I create instances of classes from a list of classes? I've looked at other SO answers but did understand them.
I have a list of classes:
list_of_classes = [Class1, Class2]
Now I want to create instances of those classes, where the variable name storing the class is the name of the class. I have tried:
for cls in list_of_classes:
str(cls) = cls()
but get the error: "SyntaxError: can't assign to function call". Which is of course obvious, but I don't know what to do else.
I really want to be able to access the class by name later on. Let's say we store all the instance in a dict and that one of the classes are called ClassA, then I would like to be able to access the instance by dict['ClassA'] later on. Is that possible? Is there a better way?
You say that you want "the variable name storing the class [to be] the name of the class", but that's a very bad idea. Variable names are not data. The names are for programmers to use, so there's seldom a good reason to generate them using code.
Instead, you should probably populate a list of instances, or if you are sure that you want to index by class name, use a dictionary mapping names to instances.
I suggest something like:
list_of_instances = [cls() for cls in list_of_classes]
Or this:
class_name_to_instance_mapping = {cls.__name__: cls() for cls in list_of_classes}
One of the rare cases where it can sometimes make sense to automatically generate variables is when you're writing code to create or manipulate class objects themselves (e.g. producing methods automatically). This is somewhat easier and less fraught than creating global variables, since at least the programmatically produced names will be contained within the class namespace rather than polluting the global namespace.
The collections.NamedTuple class factory from the standard library, for example, creates tuple subclasses on demand, with special descriptors as attributes that allow the tuple's values to be accessed by name. Here's a very crude example of how you could do something vaguely similar yourself, using getattr and setattr to manipulate attributes on the fly:
def my_named_tuple(attribute_names):
class Tup:
def __init__(self, *args):
if len(args) != len(attribute_names):
raise ValueError("Wrong number of arguments")
for name, value in zip(attribute_names, args):
setattr(self, name, value) # this programmatically sets attributes by name!
def __iter__(self):
for name in attribute_names:
yield getattr(self, name) # you can look up attributes by name too
def __getitem__(self, index):
name = attribute_names[index]
if isinstance(index, slice):
return tuple(getattr(self, n) for n in name)
return getattr(self, name)
return Tup
It works like this:
>>> T = my_named_tuple(['foo', 'bar'])
>>> i = T(1, 2)
>>> i.foo
1
>>> i.bar
2
If i did understood your question correctly, i think you can do something like this using globals():
class A:
pass
class B:
pass
class C:
pass
def create_new_instances(classes):
for cls in classes:
name = '{}__'.format(cls.__name__)
obj = cls()
obj.__class__.__name__ = name
globals()[name] = obj
if __name__ == '__main__':
classes = [A, B, C]
create_new_instances(classes)
classes_new = [globals()[k] for k in globals() if k.endswith('__') and not k.startswith('__')]
for cls in classes_new:
print(cls.__class__.__name__, repr(cls))
Output (you'll get a similar ouput):
A__ <__main__.A object at 0x7f7fac6905c0>
C__ <__main__.C object at 0x7f7fac6906a0>
B__ <__main__.B object at 0x7f7fac690630>