Using class method as callback in Python - python

I have one class with constructor that recieve two functions as callback's.
class TreeTraverse:
def __init__(self, branch_head: Node, node_processor: Callable[[Phrase], None], ascent: Callable[[], None]):
self._branch_head: Node = branch_head
self._ascent: Callable[[], None] = ascent
self._node_processor: Callable[[Phrase], None] = node_processor
...
I annotated parameters using typing module: one of them should recieve 1 parameter, other - without parameters. Then I have another class which passes their methods as callbacks:
class CodeGenerator:
def phrase_processor(self, phrase: Phrase):
pass
def asc(self):
pass
def generate(self):
tree_traverse = TreeTraverse(self._tree.get_head(), node_processor=self.phrase_processor, ascent=self.asc)
tree_traverse.traverse()
The question is why this code works? Is it allowed to be left as is?
Class method has extra "self" argument at the first position. And at the place where function called I should pass class instance to this method. But appears that is not required.
My guess is that the all in python is object (including functions and methods), and this object has a stash, where self is put when I pass method.

Related

Is there a way in a class function to return an instance of the class itself?

I have a class in python with a function and I need that function to explicitly return an instance of that class. I tried this
class a(type):
def __init__(self, n):
self.n = n
def foo() -> a:
return a(self.n + 1)
but I get an error "a is not defined". What should I do? Thanks.
Since OP used annotation in member function. There is a NameError in the annotation also. To fix that. Try following:
Reference:
https://www.python.org/dev/peps/pep-0484/#id34
Annotating instance and class methods
In most cases the first argument of class and instance methods does
not need to be annotated, and it is assumed to have the type of the
containing class for instance methods, and a type object type
corresponding to the containing class object for class methods. In
addition, the first argument in an instance method can be annotated
with a type variable. In this case the return type may use the same
type variable, thus making that method a generic function.
from typing import TypeVar
T = TypeVar('T', bound='a')
class a:
def __init__(self: T, n: int):
self.n = n
def foo(self: T) -> T:
return a(self.n + 1)
print(a(1).foo().n)
Result:
2
What you are asking works:
class A:
def __init__(self, n):
self.n = n
def foo(self):
return A(self.n + 1)
a = A(1)
b = a.foo()
print(a.n, b.n)
There are sevaral problems with your original code though.
The type hint -> A does not work because A is not defined at that point.
You need to pass self to the foo method as well.
If you subclass type, and want to make use of its features, I suggest you also initialize it by calling super().__init__() and pass on all necessary arguments. You can do that at any point you prefer, but usually it's done in the __init__() method of the subclass.

Metaclasses and methods

Adding a method to metaclass works perfectly in the below example.
class Test(object):
def __init__(self, x):
self.x = x
def double(self):
return self.x*2
# method to add
def quadruple(self):
return self.x*4
# creating metaclass
TypeTest = type('TypeTest', (Test,), {'triple': triple,
'quadruple': quadruple})
# prints 8
TypeTest(2).quadruple()
The below example doesn't work and I have no idea why. It simply doesn't recognise self in the parsed function and a TypeError occurs.
class Vehicle(object):
def __init__(self, wheels, door=False):
self.wheels = wheels
self.door = door
# method to add
def check_load(self, x):
if x > self.load:
return "Load won't fit"
else:
return "Load will fit"
# creating metaclass
Truck = type('Truck', (Vehicle,), dict(wheels=4,door=True, load=100,
check_load=check_load))
# TypeError: check_load() missing 1 required positional argument: 'x'
Truck.check_load(10)
First of all: You are not creating a metaclass, you are creating regular classes. type() is the (base) metaclass here, calling it creates a new class object (the same type of object that a class statement produces).
The first type() call is essentially equivalent to:
class TypeTest(Test)
triple = triple
quadruple = quadruple
and the second example is the same as:
class Truck(Vehicle)
wheels = 4
door = True
load = 100
check_load = check_load
You forgot to create an instance of your Truck class:
Truck.check_load(10)
This leaves the check_load() function with nothing to bind to, there is no self.
In your first example you did create an instance:
TypeTest(2).quadruple()
Notice the call, passing in 2.
Create an instance for self to be bound to:
Truck(4, True).check_load(10)
If you wanted your class to not need arguments to create an instance, you'll need to provide a different __init__ method too, one that overrides the Vehicle.__init__ method:
def init(self): pass
Truck = type('Truck', (Vehicle,), dict(
wheels=4,door=True, load=100,
check_load=check_load, __init__=init))
Now you can create the instance without arguments:
Truck().check_load(10)

More efficient way of setting default method argument to instance attribute [duplicate]

I want to pass a default argument to an instance method using the value of an attribute of the instance:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
When trying that, I get the following error message:
NameError: name 'self' is not defined
I want the method to behave like this:
C("abc").process() # prints "abc"
C("abc").process("xyz") # prints "xyz"
What is the problem here, why does this not work? And how could I make this work?
You can't really define this as the default value, since the default value is evaluated when the method is defined which is before any instances exist. The usual pattern is to do something like this instead:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=None):
if formatting is None:
formatting = self.format
print(formatting)
self.format will only be used if formatting is None.
To demonstrate the point of how default values work, see this example:
def mk_default():
print("mk_default has been called!")
def myfun(foo=mk_default()):
print("myfun has been called.")
print("about to test functions")
myfun("testing")
myfun("testing again")
And the output here:
mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.
Notice how mk_default was called only once, and that happened before the function was ever called!
In Python, the name self is not special. It's just a convention for the parameter name, which is why there is a self parameter in __init__. (Actually, __init__ is not very special either, and in particular it does not actually create the object... that's a longer story)
C("abc").process() creates a C instance, looks up the process method in the C class, and calls that method with the C instance as the first parameter. So it will end up in the self parameter if you provided it.
Even if you had that parameter, though, you would not be allowed to write something like def process(self, formatting = self.formatting), because self is not in scope yet at the point where you set the default value. In Python, the default value for a parameter is calculated when the function is compiled, and "stuck" to the function. (This is the same reason why, if you use a default like [], that list will remember changes between calls to the function.)
How could I make this work?
The traditional way is to use None as a default, and check for that value and replace it inside the function. You may find it is a little safer to make a special value for the purpose (an object instance is all you need, as long as you hide it so that the calling code does not use the same instance) instead of None. Either way, you should check for this value with is, not ==.
Since you want to use self.format as a default argument this implies that the method needs to be instance specific (i.e. there is no way to define this at class level). Instead you can define the specific method during the class' __init__ for example. This is where you have access to instance specific attributes.
One approach is to use functools.partial in order to obtain an updated (specific) version of the method:
from functools import partial
class C:
def __init__(self, format):
self.format = format
self.process = partial(self.process, formatting=self.format)
def process(self, formatting):
print(formatting)
c = C('default')
c.process()
# c.process('custom') # Doesn't work!
c.process(formatting='custom')
Note that with this approach you can only pass the corresponding argument by keyword, since if you provided it by position, this would create a conflict in partial.
Another approach is to define and set the method in __init__:
from types import MethodType
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
self.process = MethodType(process, self)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
This allows also passing the argument by position, however the method resolution order becomes less apparent (which can affect the IDE inspection for example, but I suppose there are IDE specific workarounds for that).
Another approach would be to create a custom type for these kind of "instance attribute defaults" together with a special decorator that performs the corresponding getattr argument filling:
import inspect
class Attribute:
def __init__(self, name):
self.name = name
def decorator(method):
signature = inspect.signature(method)
def wrapper(self, *args, **kwargs):
bound = signature.bind(*((self,) + args), **kwargs)
bound.apply_defaults()
bound.arguments.update({k: getattr(self, v.name) for k, v in bound.arguments.items()
if isinstance(v, Attribute)})
return method(*bound.args, **bound.kwargs)
return wrapper
class C:
def __init__(self, format):
self.format = format
#decorator
def process(self, formatting=Attribute('format')):
print(formatting)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
You can't access self in the method definition. My workaround is this -
class Test:
def __init__(self):
self.default_v = 20
def test(self, v=None):
v = v or self.default_v
print(v)
Test().test()
> 20
Test().test(10)
> 10
"self" need to be pass as the first argument to any class functions if you want them to behave as non-static methods.
it refers to the object itself. You could not pass "self" as default argument as it's position is fix as first argument.
In your case instead of "formatting=self.format" use "formatting=None" and then assign value from code as below:
[EDIT]
class c:
def __init__(self, cformat):
self.cformat = cformat
def process(self, formatting=None):
print "Formating---",formatting
if formatting == None:
formatting = self.cformat
print formatting
return formatting
else:
print formatting
return formatting
c("abc").process() # prints "abc"
c("abc").process("xyz") # prints "xyz"
Note : do not use "format" as variable name, 'cause it is built-in function in python
Instead of creating a list of if-thens that span your default arguements, one can make use of a 'defaults' dictionary and create new instances of a class by using eval():
class foo():
def __init__(self,arg):
self.arg = arg
class bar():
def __init__(self,*args,**kwargs):
#default values are given in a dictionary
defaults = {'foo1':'foo()','foo2':'foo()'}
for key in defaults.keys():
#if key is passed through kwargs, use that value of that key
if key in kwargs: setattr(self,key,kwargs[key])
#if no key is not passed through kwargs
#create a new instance of the default value
else: setattr(self,key, eval(defaults[key]))
I throw this at the beginning of every class that instantiates another class as a default argument. It avoids python evaluating the default at compile... I would love a cleaner pythonic approach, but lo'.

how to use callback functions with object/method pair

I have a simple method which accepts a function to call this back later:
def SimpleFunc(parm1):
print(parm1)
class CallMe:
def __init__(self, func):
self.func = func
def Call(self, parm):
self.func(parm)
caller = CallMe(SimpleFunc)
caller.Call("Hallo")
That works fine!
But I want to use a class method and want to call the method on a defined object as callback:
class WithClassMethod:
def __init__( self, val ):
self.val = val
def Func(self, parm):
print( "WithClass: ", self.val, parm )
obj = WithClassMethod(1)
caller = CallMe( ??? )
caller.Call("Next")
How can I bind an object/method pair to a callable object?
Attention: The code from CallMe is not under my control. It comes from a webserver which needs a handler function.
You could simply pass the method object to the class:
called = CallMe(obj.Func)
To expand a bit, instance methods are really just the original class function:
>>> obj.Func.__func__
<function __main__.WithClassMethod.Func>
which, during access on an instance (obj.Func) are transformed via a descriptor (__get__) that attaches self (the instance) to them:
>>> obj.Func.__self__
<__main__.WithClassMethod at 0x7fbe740ce588>
so you can pretty much do anything you want with methods as with functions.

Class method as a decorator

I have a class where I have multiple methods. I want to use one of the methods as a decorator for other methods. For this I am using following syntax:
#self.action
def execute(self,req):
where action is other method in my class. But it doesn't work and throws exception as
name 'self' is not defined
You cannot use a method of the class while defining it; there is no self within the class nor is the class 'baked' yet to even access any class.
You can treat methods as functions to use as a decorator:
class SomeClass():
def action(func):
# decorate
return wrapper
#action
def execute(self, req):
# something
If action is defined on a base class, then you'd have to refer to the name via the base class:
class Base():
#staticmethod
def action(func):
# decorate
return wrapper
class Derived(Base):
#Base.action
def execute(self, req):
# something
For Python 2, you'd have to make action a static method here, as otherwise you get an unbound method that'll complain you cannot call it without an instance as the first argument. In Python 3, you can leave off the #staticmethod decorator there, at least for the purposes of the decorator.
But note that action cannot then be used as a method directly; perhaps it should not be part of the class at all at that point. It is not part of the end-user API here, presumably the decorator is not used by consumers of the instances of these classes.
Just beware that both the decorator and the decorated function are unbound methods, so you can only access the self (or cls for classmethods) in the inner scope of the decorator, and must manually bind the decorated method to the instance bound in the inner decorator.
class A:
x = 5
y = 6
def decorate(unbound):
def _decorator(self):
bound = unbound.__get__(self)
return bound() * self.x
return _decorator
#decorate
def func(self):
return self.y
A().func() # 30!!
Still trying to wrap my head around how decorators could be inherited and overridden.
Beware that for the decorator to work it can't be bound to an instance. That is: there is no way to make this work
a = A()
#a.decorate
def func(*args):
return 1
Despite this pattern is much more common than the asked here.
At this point the question raises: is it a method at all or just code that you happen to hide in a class?
The only way to prevent the decorator being wrongfully bound is to declare it as a staticmethod, but then it must be in a previous super class because to be used it must be bound to the static class reference which would not be yet defined, just as the self.
class A:
x = 1
#staticmethod
def decorate(unbound):
def _decorator(self):
bound = unbound.__get__(self)
return bound() * self.x
return _decorator
class B(A):
#A.decorate
def func(self):
return 1
class C():
x = 2
#B.decorate
def func(self):
return 1
a = A()
class D():
x = 3
#a.decorate
def func(self):
return 1
B().func() # 1
C().func() # 2
D().func() # 3
But as you can see, there is no way for the decorator to use the state of its own class. class A from this last example just happens to be a mixin with a default x variable and an "unrelated" static decorator.
So, again, is it a method?
To overcome all of this, you can bind the staticmethod in your same class to an arbitrary type. Namely, the builtin type will do.
class A:
x = 1
#staticmethod
def decorate(unbound):
def _decorator(self):
bound = unbound.__get__(self)
return bound() * self.x
return _decorator
#decorate.__get__(type)
def func(self):
return 1
class B:
x = 2
#A.decorate
def func(self):
return 1
class C:
x = 3
#(A().decorate) # Only for Python 3.9+, see PEP-614
def func(self):
return 1
A().func() # 1
B().func() # 2
C().func() # 3
But this features too much magic for my taste. And still not a method for my gut.
In python "self" is passed to instance methods as an argument (the first), "self" is just a convention is possible to call it "foobarbaz" (of course it would be silly)… the point is that, from the outside "self" is not defined (because its scope is the method)… you can't decorate class methods with other class methods, instead you have to write a separate class!

Categories