How to call a function from another class in python - python

class A:
def add_f(self,a,b):
return a+b
class B:
def sum_f(self,a,b,c):
return A.add_f(a,b) + c
B1= B()
print B1.sum_f(1,2,3)
Traceback (most recent call last):
File "C:/Users/wxu/Documents/test.py", line 10, in <module>
print B1.sum_f(1,2,3)
File "C:/Users/wxu/Documents/test.py", line 7, in sum_f
return A.add_f(a,b) + c
TypeError: unbound method add_f() must be called with A instance as first argument (got int instance instead)
When I don't have the self for sum_f(self,a,b,c), it gave me this error:
Traceback (most recent call last):
File "C:/Users/wxu/Documents/test.py", line 10, in <module>
print test1.sum_f(1,2,3)
TypeError: sum_f() takes exactly 3 arguments (4 given)
Why is that?
And how to call function add_f from class A in class B? Thank you for your help.

There was a few things.
When you define class methods, they must have self as the first parameter.
The part where you had an error is where you tried to call B as a variable. B is a class, and you must call it like any other class. This also applies when you are calling A() in class B.
Revised code:
class A:
def add_f(self, a, b):
return a + b
class B:
def sum_f(self, a, b, c):
return A().add_f(a, b) + c
print B().sum_f(1, 2, 3)
Update:
Thanks for taking my advice but you're still missing something. In class B you call a method from class A, but you need parentheses for that too! In class B, call class A as such:
A().add_f(a, b)

Simple fixes, you just need to open and close the brackets on your class declarations, so class B becomes class B() (Same thing applied to class A). Also, you need to add some kind of variable such as self as the first argument of each method declared within a class. You never need to pass anything as this first argument to fill self, as python will do it for you.
As for your A.add_f issue, you just need to do as you did with B, and instantiate it an A object to something like A1, and then call the method of A1.

Related

Purposely trying to break Python class inheritance, don't understand why it breaks this way

I'm exploring the limits of Python class inheritance, so I wrote a small test to see how much I can get away with - redeclaring properties and overriding functions.
class A:
val : int = 3
def foo(x: int):
print(x)
class B(A):
val : str = 'python'
def foo(x: str):
print(x)
a = A()
b = B()
a.foo(5)
b.foo('test')
print(a.val)
print(b.val)
The resulting output is surprising. I would have expected some kind of exception for redeclaring the property, but instead I get:
Traceback (most recent call last):
File "c:\Development\Playground\hello.py", line 12, in <module>
a.foo(5)
TypeError: A.foo() takes 1 positional argument but 2 were given
I don't see how it interpreting that I am providing two positional arguments for a.foo(5). Granted I am trying to break it but I still want to understand.
You need a self parameter for instance methods.
class A:
val : int = 3
def foo(self, x: int):
print(x)
class B(A):
val : str = 'python'
def foo(self, x: str):
print(x)
a = A()
b = B()
a.foo(5)
b.foo('test')
print(a.val)
print(b.val)
Output:
5
test
3
python
Now that I know you are not looking for a fix, but an idea about what the interpreter is doing I can walk you through one line of your code:
a.foo(5)
This line is just a nice way for us programmers to express the idea of calling a method (foo) on an instance (a). This is syntactic sugar and I like to think of the interpreter transforming that text to this text:
A.foo(a, 5)
and then compiling that. Now you can see, when you compare that line of code to the method that you defined: def foo(x: int): that the interpreter is going to say that the method takes one positional argument (x) but you are giving it two: (a, 5)
I think the error is produced because self was automatically passed since it's a function of a class so your functions have to take self as their first argument
class A:
val : int = 3
def foo(self, x: int):
print(x)
class B(A):
val : str = 'python'
def foo(self, x: str):
print(x)

How to correctly use functions in a class?

I'm having a difficulty implementing functions in the class ArrayQ. The task is to create a class and have three methods in it, enqueue, dequeue and isEmpty. In "self.array.append(self.array)" I'm not really sure if it's supposed to be array,self.array,self or something else in the parenthesis. The basictest() function below was given to us to controll that our class works, and it doesn't at the moment.
from array import array
class ArrayQ:
def __init__(self,array):
self.array = array
def enqueue(self):
self.array.append(self.array)
def dequeue(self):
self.array.pop(0)
def isEmpty(self):
if not self.array:
print("queue is empty")
#print(ArrayQ)
lista = []
def basictest():
q = ArrayQ(lista)
q.enqueue(1)
q.enqueue(2)
x = q.dequeue()
y = q.dequeue()
if (x == 1 and y == 2):
print("test OK")
else:
print("FAILED expexted x=1 and y=2 but got x =", x, " y =", y)
basictest()
I get the following error message:
Traceback (most recent call last):
File "d1.py", line 31, in <module>
basictest()
File "d1.py", line 22, in basictest
q.enqueue(1)
TypeError: enqueue() takes 1 positional
argument but 2 were given
So could anybody please guide me towards how I should solve this problem so I could make this code work? Among the presumably several errors in this code, why can't I use "q.enqueue(1)" to alter my list using the function, or method, written in the class?
Your problem is here.
def enqueue(self):
self.array.append(self.array)
This method takes one param: self. It's a reference of that object, so, unless you've got a classmethod your class methods must have a self param as first parameter.
Now, your basictest() function calls a q.enqueue(1) but your object q is a ArrayQ and its enqueue function has only one parameter: self. It's implicitly, you cannot use 1 as self.
So, edit enqueue in this way:
def enqueue(self, value):
self.array.append(value)
First of all, there is an incorrect indentation in the class "ArrayQ". And second, there is only one default argument is passed in the "enqueue" method definition. When you create an instance of an object, the object itself is passed as a default argument(self). In "q.enqueue(1)" you are actually passing two parameters, first is self and the second is "1".

How does the list.append work?

alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger']
>>> show('tiger','cat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (2 given)
>>> show('tiger','cat', {'name':'tom'})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (3 given)
Since the method append of alist only accepts one argument, why not detect a syntax error on the line alist.append(*args, **kwargs) in the definition of the method show?
It's not a syntax error because the syntax is perfectly fine and that function may or may not raise an error depending on how you call it.
The way you're calling it:
alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger']
>>> show('tiger','cat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (2 given)
A different way:
alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger', 'tiger']
>>> class L: pass
...
>>> alist = L()
>>> alist.append = print
>>> show('tiger','cat')
tiger cat
<__main__.L object at 0x000000A45DBCC048>
Python objects are strongly typed. The names that bind to them are not. Nor are function arguments. Given Python's dynamic nature it would be extremely difficult to statically predict what type a variable at a given source location will be at execution time, so the general rule is that Python doesn't bother trying.
In your specific example, alist is not in the local scope. Therefore it can be modified after your function definition was executed and the changes will be visible to your function, cf. code snippets below.
So, in accord with the general rule: predicting whether or not alist will be a list when you call .append? Near-impossible. In particular, the interpreter cannot predict that this will be an error.
Here is some code just to drive home the point that static type checking is by all practical means impossible in Python. It uses non-local variables as in your example.
funcs = []
for a in [1, "x", [2]]:
def b():
def f():
print(a)
return f
funcs.append(b())
for f in funcs:
f()
Output:
[2] # value of a at definition time (of f): 1
[2] # value of a at definition time (of f): 'x'
[2] # value of a at definition time (of f): [2]
And similarly for non-global non-local variables:
funcs = []
for a in [1, "x", [2]]:
def b(a):
def f():
print(a)
a = a+a
return f
funcs.append(b(a))
for f in funcs:
f()
Output:
2 # value of a at definition time (of f): 1
xx # value of a at definition time (of f): 'x'
[2, 2] # value of a at definition time (of f): [2]
It's not a syntax error because it's resolved at runtime. Syntax errors are caught initially during parsing. Things like unmatched brackets, undefined variable names, missing arguments (this is not a missing argument *args means any number of arguments).
show has no way of knowing what you'll pass it at runtime and since you are expanding your args variable inside show, there could be any number of arguments coming in and it's valid syntax! list.append takes one argument! One tuple, one list, one int, string, custom class etc. etc. What you are passing it is some number elements depending on input. If you remove the * it's all dandy as its one element e.g. alist.append(args).
All this means that your show function is faulty. It is equipped to handle args only when its of length 1. If its 0 you also get a TypeError at the point append is called. If its more than that its broken, but you wont know until you run it with the bad input.
You could loop over the elements in args (and kwargs) and add them one by one.
alist = []
def show(*args, **kwargs):
for a in args:
alist.append(a)
for kv in kwargs.items():
alist.append(kv)
print(alist)

Can you modify a package's base class in python?

I've installed a python package (schematic), which has a number of classes extended from a base class.
class BaseType(object):
def __init__(self, required=False, default=None ...)
...
class StringType(BaseType):
...
class IntType(BaseType):
...
I would like to be able to modify the BaseType class, so it would accept additional constructor variables.
I know I could define my own classes based on these, but I was wondering if there's actually a way in Python to modify just the base class?
Thank you, Ben
Of course you can. Simply do BaseClass.__init__ = your_new_init. This does not work if the BaseClass is implemented in C however(and I believe you cannot reliably change a special method of a class implemented in C; you could do this writing in C yourself).
I believe what you want to do is a huge hack, that will only cause problems, so I strongly advise you to not replace __init__ of a base class that you didn't even write.
An example:
In [16]: class BaseClass(object):
...: def __init__(self, a, b):
...: self.a = a
...: self.b = b
...:
In [17]: class A(BaseClass): pass
In [18]: class B(BaseClass): pass
In [19]: BaseClass.old_init = BaseClass.__init__ #save old init if you plan to use it
In [21]: def new_init(self, a, b, c):
...: # calling __init__ would cause infinite recursion!
...: BaseClass.old_init(self, a, b)
...: self.c = c
In [22]: BaseClass.__init__ = new_init
In [23]: A(1, 2) # triggers the new BaseClass.__init__ method
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-23-09f95d33d46f> in <module>()
----> 1 A(1, 2)
TypeError: new_init() missing 1 required positional argument: 'c'
In [24]: A(1, 2, 3)
Out[24]: <__main__.A at 0x7fd5f29f0810>
In [25]: import numpy as np
In [26]: np.ndarray.__init__ = lambda self: 1 # doesn't work as expected
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-26-d743f6b514fa> in <module>()
----> 1 np.ndarray.__init__ = lambda self: 1
TypeError: can't set attributes of built-in/extension type 'numpy.ndarray'
You can probably edit the sourcefiles where the base class is defined, or make a copy of the package and edit the source for your specific project.
See also: How do I find the location of my Python site-packages directory?

Convert partial function to method in python

Consider the following (broken) code:
import functools
class Foo(object):
def __init__(self):
def f(a,self,b):
print a+b
self.g = functools.partial(f,1)
x=Foo()
x.g(2)
What I want to do is take the function f and partially apply it, resulting in a function g(self,b). I would like to use this function as a method, however this does not currently work and instead I get the error
Traceback (most recent call last):
File "test.py", line 8, in <module>
x.g(2)
TypeError: f() takes exactly 3 arguments (2 given)
Doing x.g(x,2) however works, so it seem the issue is that g is considered a "normal" function instead of a method of the class. Is there a way to get x.g to behave like a method (i.e implicitly pass the self parameter) instead of a function?
There are two issues at hand here. First, for a function to be turned into a method it must be stored on the class, not the instance. A demonstration:
class Foo(object):
def a(*args):
print 'a', args
def b(*args):
print 'b', args
Foo.b = b
x = Foo()
def c(*args):
print 'c', args
x.c = c
So a is a function defined in the class definition, b is a function assigned to the class afterwards, and c is a function assigned to the instance. Take a look at what happens when we call them:
>>> x.a('a will have "self"')
a (<__main__.Foo object at 0x100425ed0>, 'a will have "self"')
>>> x.b('as will b')
b (<__main__.Foo object at 0x100425ed0>, 'as will b')
>>> x.c('c will only recieve this string')
c ('c will only recieve this string',)
As you can see there is little difference between a function defined along with the class, and one assigned to it later. I believe there is actually no difference as long as there is no metaclass involved, but that is for another time.
The second problem comes from how a function is actually turned into a method in the first place; the function type implements the descriptor protocol. (See the docs for details.) In a nutshell, the function type has a special __get__ method which is called when you perform an attribute lookup on the class itself. Instead of you getting the function object, the __get__ method of that function object is called, and that returns a bound method object (which is what supplies the self argument).
Why is this a problem? Because the functools.partial object is not a descriptor!
>>> import functools
>>> def f(*args):
... print 'f', args
...
>>> g = functools.partial(f, 1, 2, 3)
>>> g
<functools.partial object at 0x10042f2b8>
>>> g.__get__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'functools.partial' object has no attribute '__get__'
There are a number of options you have at this point. You can explicitly supply the self argument to the partial:
import functools
class Foo(object):
def __init__(self):
def f(self, a, b):
print a + b
self.g = functools.partial(f, self, 1)
x = Foo()
x.g(2)
...or you would imbed the self and value of a in a closure:
class Foo(object):
def __init__(self):
a = 1
def f(b):
print a + b
self.g = f
x = Foo()
x.g(2)
These solutions are of course assuming that there is an as yet unspecified reason for assigning a method to the class in the constructor like this, as you can very easily just define a method directly on the class to do what you are doing here.
Edit: Here is an idea for a solution assuming the functions may be created for the class, instead of the instance:
class Foo(object):
pass
def make_binding(name):
def f(self, *args):
print 'Do %s with %s given %r.' % (name, self, args)
return f
for name in 'foo', 'bar', 'baz':
setattr(Foo, name, make_binding(name))
f = Foo()
f.foo(1, 2, 3)
f.bar('some input')
f.baz()
Gives you:
Do foo with <__main__.Foo object at 0x10053e3d0> given (1, 2, 3).
Do bar with <__main__.Foo object at 0x10053e3d0> given ('some input',).
Do baz with <__main__.Foo object at 0x10053e3d0> given ().
This will work. But I'm not sure if this is what you are looking for
class Foo(object):
def __init__(self):
def f(a,self,b):
print a+b
self.g = functools.partial(f,1, self) # <= passing `self` also.
x = Foo()
x.g(2)
this is simply a concrete example of what i believe is the most correct (and therefore pythonic :) way to solve -- as the best solution (definition on a class!) was never revealed -- #MikeBoers explanations are otherwise solid.
i've used this pattern quite a bit (recently for an proxied API), and it's survived untold production hours without the slightest irregularity.
from functools import update_wrapper
from functools import partial
from types import MethodType
class Basic(object):
def add(self, **kwds):
print sum(kwds.values())
Basic.add_to_one = MethodType(
update_wrapper(partial(Basic.add, a=1), Basic.add),
None,
Basic,
)
x = Basic()
x.add(a=1, b=9)
x.add_to_one(b=9)
...yields:
10
10
...the key take-home-point here is MethodType(func, inst, cls), which creates an unbound method from another callable (you can even use this to chain/bind instance methods to unrelated classes... when instantiated+called the original instance method will receive BOTH self objects!)
note the exclusive use of keyword arguments! while there might be a better way to handle, args are generally a PITA because the placement of self becomes less predictable. also, IME anyway, using *args, and **kwds in the bottom-most function has proven very useful later on.
functools.partialmethod() is available since python 3.4 for this purpose.
import functools
class Foo(object):
def __init__(self):
def f(a,self,b):
print a+b
self.g = functools.partialmethod(f,1)
x=Foo()
x.g(2)

Categories

Resources