Prevent passing argument of wrong type - python

I have very simple example: an abstract class Computer, and I am inheriting Laptop from it. I want to pass an object of this abstract class to a function ( Work() method of Programmer class), I marked passed argument as Computer type. I've create object of type Laptop and pass it, it's working fine.
If, for example, I pass an object of another type I will get an error that Process() method not found, python is executing line by line this function and not checking the argument type. Is there any option to get an error of wrong type on the stage of passing argument to a Process() function when passing wrong type (Game for example)? Here is my simple example:
from abc import ABC, abstractclassmethod
class Computer(ABC):
#abstractclassmethod
def Process(self):
pass
class Laptop(Computer):
def Process(self):
print("It's running")
class Programmer:
def Work(self, com: Computer):
com.Process()
class Game():
def Play(self):
pass
laptop = Laptop()
programmer = Programmer()
programmer.Work(laptop)

Related

Why is an instance of my class object ending up as a argument in my methods?

I can't figure out what exactly is happening or why:
For whatever reason when I try to call methods on an instance of a custom class I created something keeps passing the instance itself as argument in to the methods, where as it didn't before.
I don't know exactly when it started but it was recently and may have started after I updated to the latest version of 3.9 And I've spent over an hour trying to figure this out. Anyone who can explain this and tell me how to fix it will have my gratitude.
Also I don't know if this will help, but I'm still in Python 3.9, not 3.10 yet.(I should probably update.)
Example Code:
Test Class
class cat_test_class():
def __init__(self,hat=None,gear=None,weapon=None,name="Kit"):
self.hat = hat
self.gear = gear
self.weapon = weapon
self.name = name
def __str__():
return f"{name}'s equipment:\nWeapon = {weapon}\nGear ={gear}\nHat = {hat}"
def test(*args):
print(f"# of args: {len(args)} \n {args}")
def try_dis(args):
return args
Tests and Results
Creating an Instance and test 1
kitteh = cat(hat="Platypus Controller",gear="Book of Wumbology",weapon="Bun Sword")
kitteh.test("banana",26,"Mpaw3 player")
Output
Number of args: 4
(<__main__.cat_test_class object at 0x0000014554732130>, 'banana', 26, 'Mpaw3 player')
Test 2
kitteh.test()
Output
Number of args: 1
(<__main__.cat_test_class object at 0x0000014554732130>,)
Test 3
caet = kitteh.try_dis()
caet
Output
<__main__.cat_test_class at 0x14554732130>
Test 3.1 - type(caet)##
type(caet)
Output
__main__.cat_test_class
Thank you in advance for all answers.
For whatever reason when I try to call methods on an instance of a custom class I created something keeps passing the instance itself as argument in to the methods, where as it didn't before.
In python, class methods always receive the instance as an argument unless they are decorated with #staticmethod or #classmethod. This instance will be placed in the first argument, so these are equivalent:
class TestClass:
def meth1(self):
print(self)
def meth2(this):
print(this)
But by convention we use self. I prefer it to JS's this, but even if I didn't I'd use self, because that's what everyone else does.
If you want a method not to recieve the class object, you do this:
class TestClass2(TestClass):
#staticmethod
def meth2(arg=None):
print(arg)
You would do this if you wanted to keep meth2 wrapped up in the class (because it belonged to the same unit) but it never needed to do anything with class state.
Lastly we have:
class TestClass3(TestClass2):
X = 7
def __init__(self):
self.X = 8
def meth2(self):
print(self.X) # prints 8
#classmethod
def meth3(cls):
print(class.X) # prints 7
Classmethods are passed the uninstantiated class. Note that cls is purely by convention: change it to self and it will do the same thing.
Edit Previous versions of this answer referred to memory savings by #staticmethod. Apparently I was quite wrong about that, so I have removed it.

What's the difference between calling a class decorator on a class instance and on a class definition?

class Deco:
def __init__(self, name):
self.name = name
def __call__(self, test_class):
def inner_func(whatisit):
return whatisit
test_class.method = inner_func
return test_class
class TestClass:
def __init__(self, name):
self.name = name
#Deco('deco')
class TestClassWrapped:
def __init__(self, name):
self.name = name
test = TestClass('test')
test = Deco('deco')(test)
test_wrapped = TestClassWrapped('test')
print(test.method('whatisit')) >> whatisist
print(test_wrapped == test_wrapped.method()) >> True
Why do test.method and test_wrapped.method return different results ?
It seems that the first argument in test_wrapped.method is self, while it isn't for test.method. Why does it differ from one to the other?
The difference isn't how the decoration works, but how calling works. When an instance calls a method defined on its class, as test_wrapped does, it always passes self as the first argument. Meanwhile, when an object calls an attribute of itself that happens to be a function, but doesn't exist on its class, it calls it without passing self. Consider this simple class:
class LoudTalker:
def __init__(self, name):
self.shout_hello = lambda: print("HELLO, I'M {}".format(name.upper()))
>>> john = LoudTalker("John")
>>> LoudTalker.shout_hello()
HELLO, I'M JOHN
Note that john did not pass self to shout_hello (which would have thrown the error <lambda>() takes 0 positional arguments but 1 was given) because shout_hello was defined directly on the instance, not on the class.
Walking through your code step-by-step:
You create a regular TestClass named test.
You manually call Deco and provide it with test, with the line test = Deco('deco')(test).
This makes your code go through the __call__ function, which modifies the passed class test to set its method attribute to the nested function. It then returns it, and so test now contains a successfully modified TestClass : calling test.method('whatisit') will successfully return 'whatisit'. Importantly, you're NOT accessing a method here : you're accessing a FUNCTION through an ATTRIBUTE. self is passed to every method of classes in Python, but since this isn't a method, it doesn't come into play here. Try printing type(test.method), you'll see <class 'function'> and not <class 'method'>. Importantly, you've passed an INSTANCE of a TestClass, not the class definition itself : and only this instance named test has had its method attribute set.
You then create a TestClassWrapped named test_wrapped. Upon creating it, it enters the __call__ once more, passing it TestWrappedClass as the test_class parameter. Importantly, you've passed a DEFINITION of the TestWrappedClass, not an instance of it. Setting method here will modify it for every instance of TestWrappedClass you'll later create, and can even be accessed without instantiating anything. Try calling TestClassWrapped.method("abc") : it will print abc without instantiating a TestClassWrapped at all. Interestingly, when set in this way, it's not set as an attribute but as a method! Try printing type(test_wrapped.method). This is what I believe to be the source of confusion.
In the case of print(test_wrapped.method()), you have to remember that every method of instantiated classes take self as their first parameter. This means that test_wrapped.method() will return self : hence why test_wrapped == test_wrapped.method(). Note that this doesn't apply to methods called from a class definition, like I've shown earlier. TestClassWrapped.method("abc") HAS to take a parameter of some sort (like abc), else it will complain it's lacking an argument.
So this is why test.method('whatisit') returns 'whatisit' and doesn't take self as parameter, and why test_wrapped.method() does return self.

Proper form to call a method in Python?

I was trying some examples on class inheritance and stumbled, on how to access the methods. In tutorials, the proper form is always Class().method(), however apparently, that's not the only solution.
class A():
def open(self):
print("class A method open")
class B():
def open(self):
print("class B method open")
def close(self):
print("class B method close")
class D(A, B):
def end_reached(self):
print("end reached")
## Why do both of the following lines work & produce the same output?
D().open()
D.open(1)
I expected the last line to give an error, however, the output is the same as from the line above. It did give an error on missing parameter, if the method gets called like D.open(), but giving it just any parameter works.
Is there any difference between the two lines?
The difference lays in the descriptor protocol, specifically in how descriptors provide Python's object-oriented features.
open is a class attribute of various classes; in each case, though, it has the type function. That type implements the descriptor protocol, via the definition of function.__get__, by returning either a function object or a method object, depending on whether open is accessed via the class itself or an instance of the class.
Instance lookup
Given
d = D()
the attribute lookup d.open is equivalent to
type(d).__dict__['open'].__get__(d, D)
which returns a method which wraps the original function. When called, the method passes a reference to d and any of its own arguments to the wrapped function.
f = d.open # The return value of __get__
f() # open(d)
Class lookup
The attribute lookup D.open is equivalent to
D.__dict__['open'].__get__(None, D)
which returns the function open itself.
f = D.open # The return value of __get__
f(1) # open(1)
In order to learn about this, I have to go in depth about the properties of functions and methods.
Accessing a class field of a class via its instances checks if there are __set__ and/or __get__ methods. If they are, the call happens via __get__.
Functions work this way: if a function is a class field of a class (in short, methods), they are called via function.__get__(object)(*args, **kwargs).
And here, we have a difference between Python 2 and Python 3:
Python 2 unconditionally prepends the given object to the argumends and calls the underlying function with function(object, *args, **kwargs). object gets assigned to self then, which by convention is the name of the first parameter of a method.
Python 3 checks if the given object is an instance of the class as it should be. If it isn't, we get an exception. If it is, the call gets processed as above.
Example:
def outer_f(self):
return "f called with", self
class C(object):
f = outer_f
def g(self):
return "g called with", self
c = C()
print(outer_f(c))
print(C.f(c))
print(C.f.__get__(c)())
print(c.f())
print(C.g(c))
print(C.g.__get__(c)())
print(c.g())
class A():
def open(self):
print(self)
print("class A method open")
class B():
def open(self):
print("class B method open")
def close(self):
print("class B method close")
class D(A, B):
def end_reached(self):
print("end reached")
## Why do both of the following lines work & produce the same output?
D().open() # Okay
D.open(1) # BAD
output
<__main__.D object at 0x0045FE90>
class A method open
1
class A method ope
You might want to look at a static method
class A():
#staticmethod
def open():
print("open ran")
class D(A):
def end_reached(self):
print("end reached")
D.open() # Good
output
open ran
That's not an inheritance problem.
As you can see in your method declaration, first argument is self.
D() just creates an object of class D. If you do anything on the object, that object is automatically passed as first argument.
However, when doing D.open(1) you don't invoke the method on the object, so the first argument in the method is first argument passed to it == your code assumes self=1. Because your methods behave like static methods (you don't use self inside them), you don't access self's inside, so the code doesn't break.
But if your open method invoked anything from self (another method or a field),
then you would get an attribute error - because int object probably doesn't have a method like that.
Examples from build-in classes:
"aaa".split()
str.split("aaa")
They both behave the same.
"aaa" is of type str, so interpreted looks into class str for the method and automatically passes "aaa" as self. (Just like your D() is of type D. And d=D(), then d.open() will still know the type.)
The other one shows which class to ask for the method (str) but still needs an argument self. The way we define methods shows that "self" is really an argument as well, so we can just pass it.

Python class instance variables inheritance mechanics

If I run this code:
class Super:
def __init__(self, name):
self.name = name
class Sub(Super):
def publ(self):
print("works")
a = Sub()
as planned it succesfully fails with this message:
TypeError: __init__() takes exactly 2 arguments (1 given)
but it gives me bugging question:
Where the information is stored about Sub class requirement for the argument entry? I mean how SUB() knows that it needs "name" as the argument?
What is mechanism defining how it inherits "that knowledge" from Super().
The Python Tutorial has a section on inheritance.
if a requested attribute is not found in the class, the search proceeds to look in the base class. This rule is applied recursively if the base class itself is derived from some other class.
What happens when you call Sub() is that the interpreter looks in the class object for an attribute named __init__. If it finds one it will use it. If it doesn't find it (which is the case in your example), it will search through the class's base classes. In your example it finds the __init__ method inherited from class Super. The interpreter will proceed to call that method.
You can fix your example in either of the following ways:
a = Sub('John Doe')
or:
class Sub(Super):
def __init__(self):
super(Sub, self).__init__("My Name Is Sub")
def publ(self):
print("works")

Call a function inside a class when calling the class in python

This simple example is what I dont get to work or understand in my more complex script:
class printclass():
string="yes"
def dotheprint(self):
print self.string
dotheprint(self)
printclass()
When the class is called, I expected it to run the function, but instead it will tell me that "self is not defined". Im aware this happens on the line:
dotheprint(self)
But I dont understand why. What should I change for the class to run the function with the data it already has within? (string)
You misunderstand how classes work. You put your call inside the class definition body; there is no instance at that time, there is no self.
Call the method on the instance:
instance = printclass()
instance.dotheprint()
Now the dotheprint() method is bound, there is an instance for self to refer to.
If you need dotheprint() to be called when you create an instance, give the class an __init__ method. This method (the initializer) is called whenever you create an instance:
class printclass():
string="yes"
def __init__(self):
self.dotheprint()
def dotheprint(self):
print self.string
printclass()
You really need to understand Object-Oriented Programming and its implementation in Python.
You cannot "call" a class like any function. You have to create an instance, which has a lifetime and methods linked to it :
o = printclass() # new object printclass
o.dotheprint() #
A better implementation of your class
class printclass():
string="yes" #beware, this is instance-independant (except if modified later on)
def dotheprint(self):
print self.string
def __init__(self): # it's an initializer, a method called right after the constructor
self.dotheprint()

Categories