Automatic type inferring conventions in PyCharm - python

My python 2.7 project has a class called WorldTables. I pass instances of it to many methods, always under the variable name world_tables.
Unless I specify the PyCharm type hint for every method, I don't get code completion for this class (which I want to have).
However, PyCharm does seem to have a solution for this. If the class name was one word (for example, Player) it would automatically assume that every variable called player is an instance of this class.
After playing around with it a bit, I noticed that this would happen for WorldTables if I passed it under the name of worldtables (instead of world_tables which I currently use). However, that is not how the naming conventions work AFAIK.
Is there a solution to this that doesn't involve adding (hundreds of) type hints or breaking my naming conventions? Something like:
A) Telling pycharm to automatically assume that class_name is ClassName rather than that classname is of ClassName
B) Giving PyCharm a one-off type hint ("every time you see a variable called class_name, assume it is of class ClassName")
C) Any other creative idea that would address this issue.
Thanks!

If you specify the type in your docstrings, PyCharm picks up on it.
For eg.
def some_func(word_tables):
"""The description of the function.
:param `WordTables` word_tables: An instance of the `WordTables` class.
"""
pass

Related

Changing Python class methods after class definition–is it harmful/harmless/useful/useless?

I found out that it is possible to assign to class variables after class definitions and that methods are technically class variables. So I tried the following, which appeared to work.
class Fruit():
def __init__(self, name, price):
self.name = name
self.price = price
a = Fruit('apple', 5)
Fruit.__init__ = lambda self: None
b = Fruit()
Can something like this potentially break things? On the other hand, is there a practical situation where this can be useful?
Of course it can and will break things. Any other code that tries to initialize a Fruit with a name and a price will now raise an exception, as the replaced constructor doesn't accept those parameters.
In general, the only practical situation is mocking/patching for tests (or certain, very rare runtime cases where there is no other way). However, that patching is best done with a library to deal with it, e.g. the standard library's unittest.mock.
It is very dangerous to have direct access to class instances for many reasons.
1- Changing the Name of an Instance Variable
The first problem with direct access is that changing the name of an
instance variable will break any client code that uses the original name directly. if the developer changes the name of an instance
variable in the class from self.originalName to self.newName, then any client software that uses the original name directly will break.
2- Changing an Instance Variable into a Calculation
A second situation where direct access is problematic is when the code of
a class needs to change to meet new requirements. Suppose that when
writing a class, you use an instance variable to represent a piece of data,
but the functionality changes so that you need an algorithm to compute a
value instead.
3- Validating Data
The third reason to avoid direct access when setting a value is that client
code can too easily set an instance variable to an invalid value. A better
approach is to call a method in the class, whose job is to set the value. As
the developer, you can include validation code in that method to ensure
that the value being set is appropriate.
So, it is always better to use getters and setters methods just in case an instance variable needs to be accessed from outside the class.
But, there are certain circumstances where it is safe to use direct access: when it is absolutely clear what the instance variable means, little or no validation of the data is needed, and there is no
chance that the name will ever change. A good example of this is the Rect
(rectangle) class in the pygame package. A rectangle in pygame is defined
using four values—x, y, width, and height—like this:
oRectangle = pygame.Rect(10, 20, 300, 300)
After creating that rectangle object, using oRectangle.x, oRectangle.y,
oRectangle.width, and oRectangle.height directly as variables seems acceptable
Source: Object-oriented python by Irv Kalb

Annotating "Instance of a subclass derived from specific base class"

I have a Python method with the following signature:
def basic_sizer(self, ctrl):
where ctrl can be any wxPython control derived from wx.Control. Is there a specific Python stock annotation to indicate this other than either
def basic_sizer(self, ctrl: wx.Control):
or
def basic_sizer(self, ctrl: Union[wx.SpinCtrl, wx.BitmapButton, <other possible controls>]):
I have tried
def basic_sizer(self, ctrl: Type[wx.Control]):
as suggested here. This approach is also presented in the official documentation, but PyCharm does not accept it, flagging mismatched type. I do not want to use some PyCharm-specific hack, even if available. Rather, I am interested in whether the Python typing module provides a generic approach for this situation.
Abstraction
You have some base class SomeBase. You want to write and annotate a function foo that takes an argument arg. That argument arg can be an instance of SomeBase or of any subclass of SomeBase. This is how you write that:
def foo(arg: SomeBase):
...
Say now there are classes DerivedA and DerivedB that both inherit from SomeBase and you realize that arg should actually only ever be an instance of any of those subclasses and not be of the type SomeBase (directly). Here is how you write that:
def foo(arg: DerivedA | DerivedB):
...
Or in Python <3.10:
from typing import Union
def foo(arg: Union[DerivedA, DerivedB]):
...
To my knowledge, there is currently no way to annotate that arg should be an instance of any subclass of SomeBase but not of the class SomeBase itself.
Concrete
I am not familiar with wxPython, but you stated that you want the argument ctrl to
be any wxPython control derived from wx.Control.
According to the documentation, wx.Control is in fact a class. Your statement is still ambiguous in whether or not the ctrl argument should be assumed to be any instance of wx.Control. But if so, you do write:
def basic_sizer(self, ctrl: wx.Control):
...
If you want to restrict it to specific subclasses, you use the Union.
But this is wrong:
def basic_sizer(self, ctrl: Type[wx.Control]):
...
That would state that ctrl must be a class (as opposed to an instance of a class), namely wx.Control or any subclass of it. Unless of course that is in fact what you want... Again, your statement is ambiguous.
Mismatched types
Possible reasons for PyCharm complaining about "mismatched types" include:
You are calling the method basic_sizer providing an argument for ctrl that is not actually an instance of wx.Control.
wxPython messed up big time in their typing.
PyCharm has a bug in its static type checker.
If you provide the code that produces the PyCharm complaint and the specific message by PyCharm, we can sort this out.
PS:
If the PyCharm complaint arises in some other place because you assume that ctrl has certain attributes that it may not have, that would probably indicate that you actually need it to be an instance of specific subclasses. There are multiple ways to handle this, depending on the situation.

Is it possible to set a Metaclass globally so it applies to all classes created by default?

I get that a metaclass can be substituted for type and define how a newly created class behaves.
ex:
class NoMixedCase(type):
def __new__(cls,clsname,base,clsdict):
for name in clsdict:
if name.lower() != name:
raise TypeError("Bad name.Don't mix case!")
return super().__new__(cls,clsname,base,clsdict)
class Root(metaclass=NoMixedCase):
pass
class B(Root):
def Foo(self): #type error
pass
However, is there a way of setting NoMixedCase globally, so anytime a new class is created it's behavior is defined by NoMixedCase by default, without havining to inherit from Root?
So if you did...
Class B:
def Foo(self):
pass
...it would still check case on method names.
As for your question, no, it it is not ordinarily - and possibly not even some extra-ordinary thng that will work for this - a lot of CPythons inner things are tied to the type class, and hardcoded to it.
What is possible of trying, without crashing the interpretrer right away, would be to write a wrapper for type.__new__ and use ctypes to replace it directly in type.__new__ slot. (Ordinary assignment won't do it). You'd probably still crash things.
So, in real life, if you decide not to go via a linter program with a plug-in and commit hooks as I suggested in the comment above, the way to go is to have a Base class that uses your metaclass, and get everyone in your project to inherit from that Base.

Python UiPath - How to use Classes with UiPath.Python.Activities

Here is an image showing Python scope activity (version 3.6 and target x64):
Python Scope
The main problem is the relation between both invoke python methods, the first one is used to start the class object, and the second one to access a method of that class. Here is an image of the first invoke python properties:
Invoke Python init method
And the getNumberPlusOne activity call:
Invoke Python getNumberPlusOne method
The python code being executed:
class ExampleClass:
def __init__(self,t,n):
self.text = t
self.number = n
def getNumberPlusOne(self):
return (self.number+1)
And finally, the error when executing the second Invoke Python Method:
An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
System.InvalidOperationException: Error invoking Python method ----> System.Runtime.Serialization.InvalidDataContractException: Type 'UiPath.Python.PythonObject' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute. See the Microsoft .NET Framework documentation for other supported types.
Any idea about where is the mistake and how to interact with the output object created in the init method?
I believe that this activity was designed with simple scripts in mind, not with entire classes. Here's an article on their Community Forum where user Sergiu.Wittenberger goes into more details.
Let's start with the Load Python Script activity:
In my case the local variable "pyScript" is a pointer to the python object, i.e. an instance of ExampleClass.
Now, there is the Invoke Python Method activity - this one allows us to call a method by name. It seems however that methods on the class are inaccessible to UiPath - you can't just type pyScript.MethodName().
So it seems that we can't access class methods (please proof me wrong here!), but there's a workaround as shown by Sergio. In your case, you would add another method outside your class in order to access or manipulate your object:
class ExampleClass:
def __init__(self,t,n):
self.text = t
self.number = n
def getNumberPlusOne(self):
return (self.number+1)
foo = ExampleClass("bar", 42)
def get_number_plus_one():
return foo.getNumberPlusOne()
Note that this also means that the object is instantiated within the very same file: foo. At this point this seems to be the only option to interact with an object -- again, I'd hope somebody can prove me wrong.
For the sake of completeness, here's the result:
I would like to add to what the above user said that you have to make sure that the imports you use are in the global site-packages, and not in a venv as Studio doesn't have access to that.
Moreoever, always add this:
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
to the beginning of your code. Again, a limitation of the implementation. (docs here: https://docs.uipath.com/activities/docs/load-script)
Doing this you might be able to do more complicated structures I think, but I haven't tested this out.

Python constructors convention

I wonder if there is any convention regarding constructor in Python. If I have a constructor doing nothing, I can basically not writing it and everything will work just fine.
However when I'm using Pycharm, it is recommending me (warning) to write an empty constructor:
__init__:
pass
I have not find anything regarding this problem in the PEP8. I am wondering if Pycharm just come out with a new convention or if there is a reason behind that ?
Thanks.
I think it's opinion based, but I will share rules that I try to follow:
1. Declare all instance variables in constructor, even if they are not set yet
def __init__(self, name):
self.name = name
self.lname = None
Do not do any logic in the constructor. You will benefit from this when will try to write unittests.
And of course if it's not necessary dont' add it.
I agree with the sentiment to not write unnecessary code. The warning is probably there to help speed up development since most classes probably have an init and this will remind you to write it ahead of time.
It is possible to customize or suppress this warning in Settings -> Editor -> Inspections -> Python -> "Class has no __init__ method"
Don't add a constructor if it doesn't do anything. Some editors like to warn you for some silly things. Eclipse for example, warns you when variables are initialized but not used later on or when classes don't have a serializable id. But that's Java. If your program will run without it, then remove the constructor.
I keep seeing this when I make a tiny, ad hoc class. I use this warning as a cue:
I forgot to derive the class from something sensible.
Exception Object
For example, an exception class that I would use to raise CustomException.
No:
class CustomException:
pass
Yes:
class CustomException(Exception):
pass
Because Exception defines an __init__ method, this change makes the warning go away.
New Style Object
Another example, a tiny class that doesn't need any member variables. The new style object (explicit in Python 2, implicit in Python 3) is derived from class object.
class SettClass(object)
pass
Similarly, the warning goes away because object has an __init__ method.
Old Style Object
Or just disable the warning:
# noinspection PyClassHasNoInit
class StubbornClass:
pass

Categories

Resources