In Python 3 how do you add type annotations for a class variable that may or may not exist. This is distinct from a class variable that may be None. For example:
from typing import Optional
class Foo:
a: Optional[int]
# b: ???
def __init__(self, has_a: bool, has_b: bool):
if has_a:
self.a = 1
else
self.a = None
if has_b:
self.b = 2
Then
>>> x = Foo(False, False)
>>> repr(x.a)
'None'
>>> repr(x.b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'b'
What do I write in ??? to indicate to type checkers that b may be an int or it may not exist at all?
Related
Let's say we have an attrs class:
#attr.s()
class Foo:
a: bool = attr.ib(default=False)
b: int = attr.ib(default=5)
#b.validator
def _check_b(self, attribute, value):
if not self.a:
raise ValueError("to define 'b', 'a' must be True")
if value < 0:
raise ValueError("'b' has to be a positive integer")
So the following behaviour is correct:
>>> Foo(a=True, b=10)
Foo(a=True, b=10)
>>> Foo(b=10)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<attrs generated init __main__.Foo>", line 5, in __init__
__attr_validator_b(self, __attr_b, self.b)
File "<input>", line 9, in _check_b
ValueError: to define 'b', 'a' must be True
But this is not:
>>> Foo()
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<attrs generated init __main__.Foo>", line 5, in __init__
__attr_validator_b(self, __attr_b, self.b)
File "<input>", line 9, in _check_b
ValueError: to define 'b', 'a' must be True
This obviously happens because Foo.b is always initialized, regardless of when Foo.a is given value: via default value or on Foo.__init__.
Is there anyway to accomplish this attribute dependance with any of the initialization hooks?
Following #hynek's recommendation to have default values that result in a valid instance, I've changed default of the dependant attribute to None to validate only when a value is passed on __init__:
#attr.s()
class Foo:
a: bool = attr.ib(default=False)
b: Optional[int] = attr.ib(default=None)
#b.validator
def _check_b(self, attribute, value):
if value is None:
self.b = 5
return
if not self.a:
raise ValueError("to define 'b', 'a' must be True")
if value < 0:
raise ValueError("'b' has to be a positive integer")
I am aware that to change attributes's values on a validator is not optimal, but it gets the work done.
Documentation about validators can be found here
Depending what you want to do, you can check whether value is the default value (available on attribute.default). However, your whole problem is that the default values result in an instance that's, according to your validator, invalid. Thus there's probably better ways to model this.
In Python 3, prefixing a class variable makes it private my mangling the name within the class. How do I access a module variable within a class?
For example, the following two ways do not work:
__a = 3
class B:
def __init__(self):
self.a = __a
b = B()
results in:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
NameError: name '_B__a' is not defined
Using global does not help either:
__a = 3
class B:
def __init__(self):
global __a
self.a = __a
b = B()
results in:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
NameError: name '_B__a' is not defined
Running locals() shows that the variable __a exists unmangled:
>>> locals()
{'__package__': None, '__name__': '__main__',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__doc__': None, '__a': 3, 'B': <class '__main__.B'>,
'__builtins__': <module 'builtins' (built-in)>, '__spec__': None}
[Newlines added for legibility]
Running same code in a module (as opposed to interpreter) results in the exact same behavior. Using Anaconda's Python 3.5.1 :: Continuum Analytics, Inc..
It's ugly but You could access globals:
__a = 3
class B:
def __init__(self):
self.a = globals()["__a"]
b = B()
You can also put it in a dict:
__a = 3
d = {"__a": __a}
class B:
def __init__(self):
self.a = d["__a"]
b = B()
Or a list, tuple etc.. and index:
__a = 3
l = [__a]
class B:
def __init__(self):
self.a = l[0]
b = B()
Apparently the "official" answer is not to use double underscores outside of a class. This is implied in the documentation here: https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references. Furthermore, the following (failed) bug report (and this response) make it explicit.
You are instantiating a class by passing a variable which is not defined. putting __a outside the class will not not work as the class will not see this variable. What you should do instead is:
__a = 3
class B:
def __init__(self, __a):
self.a = __a
b = B(__a)
This way you would have passed an argument in the constructor for initializing.
If you are going to mangle the names as you are trying to do then I would refer you to this article: http://shahriar.svbtle.com/underscores-in-python
As such, my solution to what you are trying to do is as follows:`
class R:
global _R__a
_R__a = 3
def __init__(self):
pass
class B:
global _R__a
def __init__(self):
self.a = _R__a
b = B()
print b.a
#3`
This way, you are also more specific about the variable you are calling without much room for modifying it later. Hope this works.
When I access an attribute from the parent class via the child class like this all works fine:
class A():
a=1
b=2
class B(A):
c=3
d=B.a+B.b+B.c
print d
But if I try to access an attribute from the parent class inside the child class like this, it doesn't work:
class A():
a=1
b=2
class B(A):
c=3
d=a+b+c
print d
I receive the error: name 'a' is not defined
Let assume that I have many equation like d=a+b+c (but more complicated) and I can't edit them - I have to call in class B "a" as "a", not "self.a" or "something.a". But I can, before equations, do A.a=a. But it is not the smartest way to reload all variables manually. I want to bypass it using inheritance. Is it possible or i should do all manually? Or maybe it is 3th route in this code?
During the class definition, none of the inherited attributes are available:
>>> class Super(object):
class_attribute = None
def instance_method(self):
pass
>>> class Sub(Super):
foo = class_attribute
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
class Sub(Super):
File "<pyshell#7>", line 2, in Sub
foo = class_attribute
NameError: name 'class_attribute' is not defined
>>> class Sub(Super):
foo = instance_method
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
class Sub(Super):
File "<pyshell#9>", line 2, in Sub
foo = instance_method
NameError: name 'instance_method' is not defined
You can't even access them using super, as the name of the subclass isn't defined within the definition block*:
>>> class Sub(Super):
foo = super(Sub).instance_method
Traceback (most recent call last):
File "<pyshell#11>", line 1, in <module>
class Sub(Super):
File "<pyshell#11>", line 2, in Sub
foo = super(Sub).instance_method
NameError: name 'Sub' is not defined
The only way to access the inherited attributes at definition time is to do so explicitly, using the name of the superclass:
>>> class Sub(Super):
foo = Super.class_attribute
>>> Sub.foo is Super.class_attribute
True
Alternatively you can access them within class or instance methods, but then you need to use the appropriate prefix of the class (conventionally cls) or instance (conventionally self) parameter.
* for anyone thinking "ah, but in 3.x you don't need arguments to super":
>>> class Sub(Super):
foo = super().instance_method
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
class Sub(Super):
File "<pyshell#6>", line 2, in Sub
foo = super().instance_method
RuntimeError: super(): no arguments
That's only true inside instance/class methods!
I may be wrong on this, but are you sure you don't want rather this?
class A(object):
def __init__(self):
self.a = 1
self.b = 2
class B(A):
def __init__(self):
super(B, self).__init__()
self.c = 3
#property
def d(self):
return self.a + self.b + self.c
BB = B()
print BB.d
or, as jonrsharpe pointed out:
class A():
a=1
b=2
class B(A):
c=3
d=A.a+A.b+c
print B.d
I want to do something like this:
class A:
def methodA(self):
return 5
class B:
def methodB(self):
return 10
class X(...):
def __init__(self, baseclass):
if baseclass =='A' : derive X from A
elif baseclass == 'B' : derive X from B
else: raise Exception("Not supported baseclass %s!" % (baseclass))
def methodX(self):
return 42
X('A').methodA() # returns 5
X('A').methodX() # returns 42
X('A').methodB() # methodB not defined
X('B').methodB() # returns 10
X('B').methodX() # returns 42
X('A').methodA() # methodA not defined
How can I implement this?
If you want to add methodX to the existing classes, you could consider multiple inheritance:
class A:
def methodA(self):
return 5
class B:
def methodB(self):
return 10
class X():
#classmethod
def new(cls, baseclass):
if baseclass == A:
return AX()
elif baseclass == B:
return BX()
else: raise Exception("Not supported baseclass %s!" % str(baseclass))
def methodX(self):
return 42
class AX(A, X):
pass
class BX(B, X):
pass
You can add args and kwargs to X.new and pass them on to the specific constructors. Here are the outputs of your tests (I corrected the last on in your question):
>>> ax = X.new(A)
>>> ax.methodA() # returns 5
5
>>> ax.methodX() # returns 42
42
>>> ax.methodB() # methodB not defined
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: AX instance has no attribute 'methodB'
>>> bx = X.new(B)
>>> bx.methodB() # returns 10
10
>>> bx.new(B).methodX() # returns 42
42
>>> bx.new(B).methodA() # methodA not defined
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: BX instance has no attribute 'methodA'
You should define two classes, X and Y, and a factory-method to instantiate either X or Y, depending on a parameter.
In general, the behavior you try to implement is somewhat confusing. When you create an instance (that is what X(...) does) you should get an instance of X, and instances of a class should have same attributes. That is one of the main reasons why classes exist.
Example:
class A:
def methodA(self):
return 5
class B:
def methodB(self):
return 10
def x(class_name):
name2class = {"A":A, "B":B}
return name2class[class_name]()
for name in ["A","B","C"]:
instance = x(name)
print name, instance
will print
A <__main__.A instance at 0x022C8D50>
B <__main__.B instance at 0x022C8DF0>
Traceback (most recent call last):
File ".../14834949.py", line 21, in <module>
instance = x(name)
File ".../14834949.py", line 18, in x
return name2class[class_name]()
KeyError: 'C'
Consider such code:
class A ():
name = 7
description = 8
color = 9
class B(A):
pass
Class B now has (inherits) all attributes of class A. For some reason I want B not to inherit attribute 'color'. Is there a possibility to do this?
Yes, I know, that I can first create class B with attributes 'name' and 'description' and then inherit class A from B adding attribute 'color'. But in my exact case, B is actually a reduced version of A, so for me it seems more logical to remove attribute in B (if possible).
I think the best solution would be to change your class hierarchy so you can get the classes you want without any fancy tricks.
However, if you have a really good reason not to do this you could hide the color attribute using a Descriptor. You'll need to be using new style classes for this to work.
class A(object):
name = 7
description = 8
color = 9
class Hider(object):
def __get__(self,instance,owner):
raise AttributeError, "Hidden attribute"
def __set__(self, obj, val):
raise AttributeError, "Hidden attribute"
class B(A):
color = Hider()
You'll then get an AttributeError when you try to use the color attribute:
>>> B.color
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __get__
AttributeError: Hidden attribute
>>> instance = B()
>>> instance.color
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __get__
AttributeError: Hidden attribute
>>> instance.color = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __set__
AttributeError: Hidden attribute
You can supply a different value for color in B, but if you want B not to have some property of A then there's only one clean way to do it: create a new base class.
class Base():
name = 7
description = 8
class A(Base):
color = 9
class B(Base):
pass