Accessing a class variable through 'self' - python-3.x

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...

Related

Usage of __setattr__ to rewrite whole method of library class issue: missing 1 required positional argument: 'self'

I've got some imported packages with tricky structure
and need to call some method that bases on lots of other methods
with non-default parameters, which are not class attributes themself like pipeline in sklearn.
Minimal example of this module structure:
class Library_class:
def __init__(
self,
defined_class_options,
):
self.defined_class_options = defined_class_options
def method1( self , default_non_class_arg = 12 ):
assert self.defined_class_options==3
return default_non_class_arg
def method2( self, image ):
return image/ self.method1()
Default usage:
class_instance = Library_class( 3 )
class_instance.method2( 36 )
> 3.0
I need to set default_non_class_arg to 6 for example.
I've tried multiple approaches:
Analogous to https://stackoverflow.com/a/35634198/7607734
class_instance.method2( 36 ,
method1__default_non_class_arg=3 )
TypeError: method2() got an unexpected keyword argument 'method1__default_non_class_arg'
It don't work probably because class definitely don't have set_params
With setattr on redefined function
class_instance.__setattr__('method1',Library_class.new_method1)
class_instance.method2( 36 )
TypeError: new_method1() missing 1 required positional argument: 'self'
Both your snippets and question are quite messy, almost to the point of being unreadable.
Anyway, if you wantt to replace method1 with another function, say new_method1 in an specific instance, just do that. Your call to .__setattr__ does that, but it is not needed at all, (and if it was, due to you not having the method to be replaced name at code writting time, and needed it as a parameter, it is more correct to call the built-in setattr, not the instance method: `setattr(class_instance, "method1", new_method1").
Ordinarily, if you know, at code writting time you have to replace "method1" in an instance, the assigment operator will do it:
class_instance.method1 = new_method1
What went wrong in your examle is that if you assign a method to an instance, instead of a class, you are bypassing the mechanism that Python uses to insert the self attribute into it - so your new_method1 needs a different signature. (and this is exactly what the error message "TypeError: new_method1() missing 1 required positional argument: 'self'" is saying):
class MyClass:
...
def method1(self, param1=36):
...
...
def new_method1(param1=6): # <-- written outside of any class body, sans self
...
my_instance = MyClass()
my_instance.method1 = new_method1
this will work.
new_method1 could be written in a class body as well, and could be replaced just the same, but you would have to write it without the self parameter the same, and then it would not work straight as a normal method.
OR, you can, at assigment time, insert the self argument yourself - the functools.partial call is a convenient way to do that:
class MyClass:
...
def method1(self, param1=36):
...
def new_method1(self, param1=6):
...
...
my_instance = MyClass()
from functools import partial
MyClass.method1 = partial(MyClass.new_method1, my_instance)
Now, this should answer what you are asking, but it would not be honest of me to end the answer without saying this is not a good design. The best thing there is to pull your parameter from another place, it might be from an instance attribute, instead of replacing the method entirely just to change it.
Since for normal attributes, Python will read the class attribute if no instance attribute exists, it will happen naturally, and all you have to do is to set the new default value in your instance.
class MyClass:
default_param_1 = 36 # Class attribute. Valid for every instance unless overriden
...
def method1(self, param1=None):
if param1 is None:
param1 = self.default_param_1 #Automatically fetched from the class if not set on the instance
...
...
my_instance = MyClass()
my_instance.default_param_1 = 6
...

How to set coc-pyright to not warn for unused self parameter in a method?

I'm using the coc-pyright extension of CoC in neovim.
There are cases where we have an instance method in a class that doesn't need to access self variables. For example, I may have some variables in __init__ method which do not need to be accessed anywhere else in the class. As a toy example, consider,
class Example:
def __init__(self, some_var):
self.another_var = self.process_var(some_var)
def process_var(self, some_var):
return some_var*2
Now, here pyright warns me that self is not accessed. Is there a way to set Pyright to not give a warning in such cases?
P.S. The variable being passed is an instance-specific variable. So self would be required to make it an instance variable, right?
So you're passing some_var to the method instead of using it as an instance variable, meaning you don't actually need self. You are using process_var as a static method, as it is being passed all of the data it needs. You can use the static method decorator and remove self like so:
class Example:
def __init__(self, some_var):
self.another_var = self.process_var(some_var)
#staticmethod
def process_var(some_var):
return some_var*2
If you were to instead use the instance variable it would look like this
class Example:
def __init__(self, some_var):
self.some_var = some_var
self.another_var = self.process_var()
def process_var(self):
return self.some_var*2
The static method approach is much cleaner here as by using instance variables you need to be aware of what the current state of some_var is before you call process_var, otherwise the results will vary.
You can set reportUnusedVariable to false in your pyrightconfig.json file, but I wouldn't recommend this as pyright is giving you valuable information about how to restructure your code.

Working with class variable in Python 3.x

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.

python property referring to property/attribute of member attribute?

I'm wondering if I have:
class A(object):
def __init__(self):
self.attribute = 1
self._member = 2
def _get_member(self):
return self._member
def _set_member(self, member):
self._member = member
member = property(_get_member, _set_member)
class B(object):
def __init__(self):
self._member = A()
def _get_a_member(self):
return self._member.member
def _set_a_member(self, member):
self._member.member = member
member = property(_get_a_member, _set_a_member)
Can I somehow avoid to write get/setters for A.member, and simply refer to the attribute or property of the A object?
Where the get/setters do logic, its of course needed, but if I simply wan't to expose the member/attributes of a member attribute, then writing get/setters seems like overhead.
I think even if I could write the get/setters inline that would help?
I find the question a bit unclear, however I try to explain some context.
Where the get/setters do logic, its of course needed, but if I simply wan't to expose the member/attributes of a member attribute
If there is no logic in getter/setters, then there is no need to define the attribute as a property, but the attribute can be used directly (in any context).
So
class A(object):
def __init__(self):
self.attribute = 1
self.member = 2
class B(object):
def __init__(self):
self.member = A()
B().member.member # returns 2
B().member.member = 10
In some languages, it's considered good practice to abstract instance properties with getter/setter methods, That's not necessarily the case in Python.
Python properties are useful when you'd need more control over the attribute, for example:
when there is logic (validation, etc.)
to define a readonly attribute (so only providing a getter without a setter)
Update (after the comment)
properties are not necessarily a tool to "hide" some internal implementation. Hiding in Python is a bit different than say in Java, due to very dynamic nature of Python language. It's always possible to introspect and even change objects on the fly, you can add new attributes (even methods) to objects on runtime:
b = B()
b.foo = 4 # define a new attribute on runtime
b.foo # returns 4
So Python developers rely more on conventions to hint their intentions of abstractions.
About the polymorphic members, I think it's most natural for Python classes to just share an interface, that's what's meant by Duck typing. So as long as your next implementation of A supports the same interface (provides the same methods for callers), it should not be any issue to change its implementation.
So this is what I came up with - use a method to generate the properties, with the assumption that the obj has an attribute of _member:
def generate_cls_a_property(name):
"""Small helper method for generating a 'dumb' property for the A object"""
def getter(obj):
return getattr(obj._member, name)
def setter(obj, new_value):
setattr(obj._member, name, new_value)
return property(getter, setter)
This allows me to add properties like so:
class B(object):
def __init__(self):
self._member = A()
member = generate_cls_a_property('member') # generates a dumb/pass-through property
I'll accept my own, unless someone tops it within a week.. :)

How can an outer class access data from its inner class?

I am trying to better understand how to use sub-classes using a very simple test-case based off of this question/answer.
class Outer():
def __init__(self, x):
self.x = super(Inner, self).__init__
# self.x = Inner(**kwargs)
def add_two(self):
""" """
return self.x + 2
class Inner():
def __init__(self, x=2):
self.x = x
res = Outer(x=3).add_two()
# res = Outer({'x' : 3}).add_two()
print(res)
>> NameError: name 'Inner' is not defined
If I run the same code but make Inner() its own separate class (as opposed to a sub-class of Outer(), I receive the following error.
TypeError: super(type, obj): obj must be an instance or subtype of type
What is the cause of this error and how do I fix this?
Nesting classes in Python (or other languages) seldom make sense. In this case, it is not useful for anything at all.
If on the "Outer" class you want to have an associated instance of "Inner", that should be created as an instance attribute, on the __init__ method for Outer - like this:
class Outer():
def __init__(self, x):
self.x = Inner(x)
# self.x = Inner(**kwargs)
def add_two(self):
""" """
return self.x + 2
class Inner():
def __init__(self, x=2):
self.x = x
Now, taking a step by step look on your original code, and trying to understand better why it does not work:
In Python everything declared in the body of a class becomes an attribute of that class - a single copy of it will be (ordinarily) shared by all instances of that class. Declaring a whole class nested is syntactically legal but gains you nothing: the inner class is not "hidden" from the outside world by the language in any sense: not by the language, neither by the conventions usually followed by Python developers.
If you want users (i.e. other programmers, or yourself in code that makes use of this file), to create instances of "Outer" and refrain from creating instances of "Inner", simply prefix its name with an _. That is a convention in Python code, and developers usually will know that they should not trust any class, function, or other name that starts with a single _ to be safe for use in 3rd party code - that is the closest Python gets to "private" or "protected" members.
Now, getting to the line:
...
self.x = super(Inner, self).__init__
It again makes no sense. super or explicitly referencing a superclass are meant to call superclasses - that is, classes from which you inherit. You created no inheritance relationship in your code, rather one of composition. That is why you get that error message - if you are using the explicit version of super, the object you pass have to be a subclass of the class you are calling super on. And it is not the case here. (Also, doing it in this form, it does not call the method - just references it - all function or method calls are comitted by the use of ())
You can also make Outer inherit from Inner - in this case, Outer will "be" an Inner, no need to keep a reference to it in an attribute - self will mean both an Outer and an Inner class.
In this case, we need a reference to "Inner" when parsing the declaration of "Outer", so it needs to be defined first:
class _Inner():
def __init__(self, x=2):
self.x = x
class Outer(_Inner):
def __init__(self, x):
super().__init__(x)
def add_two(self):
""" """
return self.x + 2
Note the use of parameterless super - one of the major changes for Python 3. If you need to write code still compatible with Python 2, the parameters to super can be explicit, and the call would be super(Outer, self).__init__().
(Again, calling it _Inner will mean that users of your class should not inherit or instantiate from _Inner and should use Outer - and that is a convention in coding style rather than language syntax)

Resources