Extending bp's answer, I wanted to show you what he meant by immutable types.
First, this is okay:
>>> class TestB():
... def __init__(self, attr=1):
... self.attr = attr
...
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1
However, this only works for immutable (unchangable) types. If the default value was mutable (meaning it can be replaced), this would happen instead:
>>> class Test():
... def __init__(self, attr=[]):
... self.attr = attr
...
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>>
Note that both a and b have a shared attribute. This is often unwanted.
This is the Pythonic way of defining default values for instance variables, when the type is mutable:
>>> class TestC():
... def __init__(self, attr=None):
... if attr is None:
... attr = []
... self.attr = attr
...
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]
The reason my first snippet of code works is because, with immutable types, Python creates a new instance of it whenever you want one. If you needed to add 1 to 1, Python makes a new 2 for you, because the old 1 cannot be changed. The reason is mostly for hashing, I believe.
Answer from Xavier Ho on Stack OverflowExtending bp's answer, I wanted to show you what he meant by immutable types.
First, this is okay:
>>> class TestB():
... def __init__(self, attr=1):
... self.attr = attr
...
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1
However, this only works for immutable (unchangable) types. If the default value was mutable (meaning it can be replaced), this would happen instead:
>>> class Test():
... def __init__(self, attr=[]):
... self.attr = attr
...
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>>
Note that both a and b have a shared attribute. This is often unwanted.
This is the Pythonic way of defining default values for instance variables, when the type is mutable:
>>> class TestC():
... def __init__(self, attr=None):
... if attr is None:
... attr = []
... self.attr = attr
...
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]
The reason my first snippet of code works is because, with immutable types, Python creates a new instance of it whenever you want one. If you needed to add 1 to 1, Python makes a new 2 for you, because the old 1 cannot be changed. The reason is mostly for hashing, I believe.
The two snippets do different things, so it's not a matter of taste but a matter of what's the right behaviour in your context. Python documentation explains the difference, but here are some examples:
Exhibit A
class Foo:
def __init__(self):
self.num = 1
This binds num to the Foo instances. Change to this field is not propagated to other instances.
Thus:
>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1
Exhibit B
class Bar:
num = 1
This binds num to the Bar class. Changes are propagated!
>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3
Actual answer
If I do not require a class variable, but only need to set a default value for my instance variables, are both methods equally good? Or one of them more 'pythonic' than the other?
The code in exhibit B is plain wrong for this: why would you want to bind a class attribute (default value on instance creation) to the single instance?
The code in exhibit A is okay.
If you want to give defaults for instance variables in your constructor I would however do this:
class Foo:
def __init__(self, num = None):
self.num = num if num is not None else 1
...or even:
class Foo:
DEFAULT_NUM = 1
def __init__(self, num = None):
self.num = num if num is not None else DEFAULT_NUM
...or even: (preferrable, but if and only if you are dealing with immutable types!)
class Foo:
def __init__(self, num = 1):
self.num = num
This way you can do:
foo1 = Foo(4)
foo2 = Foo() #use default
I am learning linked lists in python using classes.
One place that I am learning from is doing:
class Node:
def __init__(self, value):
self.value = value
self.next = NoneThe other place is doing:
class Node:
def __init__(self, value, next = None):
self.value = value
self.next = nextI can't figure out what difference it makes to use either init method. I tried to search this answer out using StackOverflow, but I had no luck.
I get that one is able to define 'next' during instantiation and the other would need to be defined after instantiation, but when would and why would you use one over the other?
edit: Thanks all. I am just starting to understand classes and still overthinking a little.
I'm wondering if this is the right way to do this. It seems... odd somehow to assign default values in the class definition, then default None in the constructor. I suppose I could give the same default values in both places, but again, this feels like unnecessarily repeating myself.
class MyClass:
name = 'Undefined'
number = -1
def __init__(self, name=None, number=None):
if name is not None:
self.name = name
if number is not None:
self.number = numberDon't use class attributes for that, that's not what they are there for. Use default arguments:
def __init__(self, name='Undefined', number=-1):
self.name = name
self.number = number
If any of the arguments are mutable, then you need to use the usual idiom:
def __init__(self, foo=None):
self.foo = [] if foo is None else foo
do you need these vars on the class level, given that they are supposed to be relevant on a per instance basis?
class MyClass:
def __init__(self, name='Undefined', number=-1):
self.name = name
self.number = number
def __str__(self):
return '{0.__class__.__name__}(name={0.name}, number={0.number})'.format(self)
print(MyClass())
print(MyClass(name='Joe'))
print(MyClass(number=11))
print(MyClass(name='Joe', number=11))
Mutable default arguments don't generally do what you want. Instead, try this:
class Node:
def __init__(self, wordList=None, adjacencyList=None):
if wordList is None:
self.wordList = []
else:
self.wordList = wordList
if adjacencyList is None:
self.adjacencyList = []
else:
self.adjacencyList = adjacencyList
Let's illustrate what's happening here:
Python 3.1.2 (r312:79147, Sep 27 2010, 09:45:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
... def __init__(self, x=[]):
... x.append(1)
...
>>> Foo.__init__.__defaults__
([],)
>>> f = Foo()
>>> Foo.__init__.__defaults__
([1],)
>>> f2 = Foo()
>>> Foo.__init__.__defaults__
([1, 1],)
You can see that the default arguments are stored in a tuple which is an attribute of the function in question. This actually has nothing to do with the class in question and goes for any function. In python 2, the attribute will be func.func_defaults.
As other posters have pointed out, you probably want to use None as a sentinel value and give each instance it's own list.
As explained by user2357112, the problem with this is that __init__ only sometimes defines the type parameter, and that explicit types can fail.
To express defaults in this way, you would need to restrict the default option to the corresponding type. You can do this with overload with a restriction on self. For example
class IntOrStrReplicator(Generic[T1]):
@overload
def __init__(self: "IntOrStrReplicator[int]") -> None:
# int variant, only, allows leaving the parameter implied
...
@overload
def __init__(self: "IntOrStrReplicator[T1]", a:T1) -> None:
# supplying the parameter requires that the type match.
...
def __init__(self, a = 4) -> None:
# This function needs programmer care because `a` still isn't checked
# but consumers of the class take the overload definitions.
self.a: T1 = a
When a Generic class has a default value in an init parameter, MyPy complains about the possibility of a type mismatch even though the init parameter defines the type.
The problem here is that the __init__ parameter does not define the type. You can do something like
x = IntOrStrReplicatorstr
where the type variable is bound to str, but the default value is still 4.
Your code isn't saying that IntOrStrReplicator() defaults to IntOrStrReplicatorint. Your code is saying that both IntOrStrReplicatorint and IntOrStrReplicatorstr use a default argument of 4. IntOrStrReplicatorstr a type error, which is why your default value is invalid.
I don't think there's any way to express what you seem to have intended.
In the absence of an explicit type argument, type checkers will attempt to infer the type, but even then, it doesn't just go by the constructor arguments. It also takes the context into account, so in the following code:
from typing import Generic, TypeVar
T = TypeVar('T')
class Foo(Generic[T]):
def __init__(self, x: T):
self.x = x
def f(x: Foo[int]): pass
def g(x: Foo[object]): pass
f(Foo(3))
g(Foo(3))
T is deduced as int for the first Foo(3) and object for the second Foo(3).