Python3 - Execute code only on class through decorator- inheritance - python-decorators

I am trying to execute a code only in one condition: when the code is executed inside the class A which inherit from class AI.
The output I wish I could have is the following:
In [1]: import maintenance
In [2]: a = maintenance.A()
In [3]: a()
Out[3]: Stopping A
Executing the common task
Cleaning logs
In [4]: b = maintenance.B()
In [5]: b()
Out[5]: Stopping B
Executing the common task
The Code is the following:
# module maintenance
1 from functools import wraps
2
The code to be executed by A is :
3 def cleaning_logs():
4 print("Cleaning logs")
5
In order not to touch the class A, I have created a decorator:
6 def archive_log(func):
7 #wraps(func)
8 def enhanced_func(*args, **kwargs):
9 func(*args,**kwargs)
10 cleaning_logs()
11 return enhanced_func
12
Because I think it is not possible to retrieve the information of the class from the above decorator, I have attempted to create a class decorator. The below code is incomplete because here is my problem:
13 def cls_archive_log(cls):
14 #... Missing Code : I have tried many things
15 #... Missing Code : I have tried many things
16 setattr(cls, '__call__', archive_log)
17 return cls
18
This class decorator that I am using in the following code:
19 #cls_archive_log
20 class AI(object):
21 def __call__(self):
22 self.stop()
23 print("Executing the common task")
24
25 class A(AI):
26 def stop(self):
27 print('Stopping A')
28
29 class B(AI):
30 def stop(self):
31 print('Stopping B')
32
But really, I have tried everything I could for the class decorator.
Any idea how I could solve my problem through a decorator, please ?

Every easy. Just set the attribute as below:
16 def maintenance(cls):
17 setattr(cls, '__call__', archive_logs(cls.__call__))
18 return cls
And decorate the class that need to implement the maintenance
31 #maintenance
32 class A(AI):
33 def stop(self):
34 print("Stopping A")
The full code is below:
#!/usr/bin/env python
from abc import ABCMeta, abstractmethod
from functools import wraps
def cleaning_logs():
print("Cleaning logs")
def archive_logs(func):
#wraps(func)
def enhanced_func(*args, **kwargs):
func(*args, **kwargs)
cleaning_logs()
return enhanced_func
def maintenance(cls):
setattr(cls, '__call__', archive_logs(cls.__call__))
return cls
class AI(object):
__metaclass__ = ABCMeta
#abstractmethod
def stop():
raise NotImplemtedError('NotImplemtedError')
def __call__(self):
self.stop()
print("Executing the common AI task")
#maintenance
class A(AI):
def stop(self):
print("Stopping A")
class B(AI):
def stop(self):
print("Stopping B")

Related

how to call __post_init__ in both subclass and baseclass which are defined by #dataclass [duplicate]

I'm trying to use the new python dataclasses to create some mix-in classes (already as I write this I think it sounds like a rash idea), and I'm having some issues. Behold the example below:
from dataclasses import dataclass
#dataclass
class NamedObj:
name: str
def __post_init__(self):
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
If I then try:
nandn = NamedAndNumbered('n_and_n')
print(nandn.name)
print(nandn.number)
I get
NumberedObj __post_init__
NamedAndNumbered __post_init__
n_and_n
1
Suggesting it has run __post_init__ for NamedObj, but not for NumberedObj.
What I would like is to have NamedAndNumbered run __post_init__ for both of its mix-in classes, Named and Numbered. One might think that it could be done if NamedAndNumbered had a __post_init__ like this:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
But this just gives me an error AttributeError: 'super' object has no attribute '__post_init__' when I try to call NamedObj.__post_init__().
At this point I'm not entirely sure if this is a bug/feature with dataclasses or something to do with my probably-flawed understanding of Python's approach to inheritance. Could anyone lend a hand?
This:
def __post_init__(self):
super(NamedObj, self).__post_init__()
super(NumberedObj, self).__post_init__()
print("NamedAndNumbered __post_init__")
doesn't do what you think it does. super(cls, obj) will return a proxy to the class after cls in type(obj).__mro__ - so, in your case, to object. And the whole point of cooperative super() calls is to avoid having to explicitely call each of the parents.
The way cooperative super() calls are intended to work is, well, by being "cooperative" - IOW, everyone in the mro is supposed to relay the call to the next class (actually, the super name is a rather sad choice, as it's not about calling "the super class", but about "calling the next class in the mro").
IOW, you want each of your "composable" dataclasses (which are not mixins - mixins only have behaviour) to relay the call, so you can compose them in any order. A first naive implementation would look like:
#dataclass
class NamedObj:
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj:
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
BUT this doesn't work, since for the last class in the mro (here NamedObj), the next class in the mro is the builtin object class, which doesn't have a __post_init__ method. The solution is simple: just add a base class that defines this method as a noop, and make all your composable dataclasses inherit from it:
class Base(object):
def __post_init__(self):
# just intercept the __post_init__ calls so they
# aren't relayed to `object`
pass
#dataclass
class NamedObj(Base):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj(Base):
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
The problem (most probably) isn't related to dataclasses. The problem is in Python's method resolution. Calling method on super() invokes the first found method from parent class in the MRO chain. So to make it work you need to call the methods of parent classes manually:
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
NamedObj.__post_init__(self)
NumberedObj.__post_init__(self)
print("NamedAndNumbered __post_init__")
Another approach (if you really like super()) could be to continue the MRO chain by calling super() in all parent classes (but it needs to have a __post_init__ in the chain):
#dataclass
class MixinObj:
def __post_init__(self):
pass
#dataclass
class NamedObj(MixinObj):
name: str
def __post_init__(self):
super().__post_init__()
print("NamedObj __post_init__")
self.name = "Name: " + self.name
#dataclass
class NumberedObj(MixinObj):
number: int = 0
def __post_init__(self):
super().__post_init__()
print("NumberedObj __post_init__")
self.number += 1
#dataclass
class NamedAndNumbered(NumberedObj, NamedObj):
def __post_init__(self):
super().__post_init__()
print("NamedAndNumbered __post_init__")
In both approaches:
>>> nandn = NamedAndNumbered('n_and_n')
NamedObj __post_init__
NumberedObj __post_init__
NamedAndNumbered __post_init__
>>> print(nandn.name)
Name: n_and_n
>>> print(nandn.number)
1

`initialize` seems to check the arguments given to `new`

I am looking at this code:
class Mo
def new(indy, rome = 1)
initialize(indy, rome)
end
def initialize(indy, rome)
...
end
end
Mo.new(2)
I get this output:
test.rb:6:in `initialize': wrong number of arguments (1 for 2) (ArgumentError)
If I add the default value for rome in the definition to new, it works:
class Mo
def new(indy, rome = 1)
initialize(indy, rome)
end
def initialize(indy, rome = 1)
...
end
end
Mo.new(2)
Why?
Because Mo.new(2) calls the method Mo.new, which by default calls the method Mo#initialize with the single argument 2 that it received, but your Mo#initialize expects two arguments.
No need for new method, because initialize is "middleware" for allocating (creating) ruby object
class Mo
def initialize(indy, rome = 1)
#indy = indy
#rome = rome
end
end
i = Mo.new(2)
This means ruby does not enter in new method
You can check it by:
puts Mo.method(:new).source_location
# => nil
but you are able to override self.new in you Mo class
def self.new(indy, rome = 1)
end
then
p Mo.method(:new).source_location
# =>["mo.rb", 2]
And it does not enter in initialize method then
P.S.
It's not good practice, but if you want to execute you code, you should call
Mo.new(2).new(3)
because your new is Mo instance method
When you write a code like
Class Foo
def new
...
end
end
You define an instance method for the object with class Foo. But when you create a new instance, you call a class' method new. If you want to rewrite method Foo.new, you should write like:
Class Foo
def self.new
....
end
end
But actually it is a bad idea to declare your own method new for classes

RubyMonk 8.2 Modules as Namespace

Overall I am having trouble understanding some concepts in Ruby, Modules being one of them.
I am on the final step in RubyMonk 8.2: Modules as Namespace and I am very lost. What should I do? My plan was to just get the solution and reverse engineer it, but there is no solution button so I am stuck :(
The instructions go as follows:
If you prepend a constant with :: without a parent, the scoping happens on the topmost level. In this exercise, change push to return 10 as per A = 10 in the topmost level, outside the Kata module.
The code already filled in is:
module Kata
A = 5
module Dojo
B = 9
A = 7
class ScopeIn
def push
A
end
end
end
end
A = 10
So, you want this:
module Kata
A = 5
module Dojo
B = 9
A = 7
class ScopeIn
def push
::A # change this line from A to ::A, meaning ::A will refer to the top-level namespaced A which is defined outside the Kata module (A = 10)
end
end
end
end
A = 10
p Kata::Dojo::ScopeIn.new.push
# => 10
If you prepend a constant with :: without a parent, the scoping happens on the topmost level. In this example, push will return 10 since A = 10 is in the topmost level, outside the Kata module.
module Kata
A = 5
module Dojo
A = 7
class ScopeIn
def push0
A
end
def push1
Kata::Dojo::A
end
def push2
Kata::A
end
def push3
::A
end
end
end
end
A = 10
scope = Kata::Dojo::ScopeIn.new #=> #<Kata::Dojo::ScopeIn:0x007fe63c8381d0>
scope.push0 #=> 7
scope.push1 #=> 7
scope.push2 #=> 5
scope.push3 #=> 10

attr_accessor or custom methods duplicate method names confusion

I do understand that Ruby supports short-hand style of calling methods i.e: 1.+(2) is same as 1+2 (and I still think if it related to my situation), but I got really confused why attr_accessor methods are neither duplicate (well, they should not be, as writer differs with = in its name) nor they differ with anything (except writer needs an argument) while accessing them outside active object.
My question is included in this code (in the second comment)
class Test
def initialize(number)
#number = number
end
def number
#number
end
def number=(n)
#number = n
end
end
t = Test.new(12)
puts t.number # => 12
t.number = 13 # Why does it do what t.number=(13) should do
puts t.number # => 13
I wonder why t.number = 13 works, when it points to a method which should only return a number and moreover how does it set a new value when t.number=(13) is not called instead.
t.number = 13 is just a shorthand for t.number=(13), they are effectively the same statement in Ruby.
attr_accessor :b creates the equivalent of the following two methods:
def b
#b
end
def b=(new_val)
#b = new_val
end
So in your code example, you could replace the two methods #number and #number= with attr_accessor :number

Local variable output is not being shown

I am trying to run a simple program:
class Pras
def samleMethod
a = 12
p "a"
end
end
There are no errors, but why is not output being shown?
Edit
class Pras
def samleMethod
a = 12
p a
end
end
class Pras
def samleMethod
a = 12
p a
end
end
In this you are creating a class named Pras with public method named sampleMethod.
I assume you have some knowledge with OOP
so when ever you are accessing a a method you need to create an instance of class to access it.
p = Pras.new
Now you can access the method using the instance
p.sampleMethod
or both in one line as
Pras.new.sampleMethod
I would suggest you to have a through look at this tuts. http://www.tutorialspoint.com/ruby/ruby_quick_guide.htm
methods will not be executed unless they are called.
Try this code online here
You are printing the string 'a' rather than the variable. Use this instead: p a
Edit:
As Rostyslav mentioned, you are not executing anything: Try Pras.new.samleMethod
Works perfectly for me:
Here we define class:
irb(main):014:0> class Pras
irb(main):015:1> def samleMethod
irb(main):016:2> a = 12
irb(main):017:2> p a
irb(main):018:2> end
irb(main):019:1> end
=> nil
Here's how to create and instance object of that class and call instance method with actually do printing:
irb(main):020:0> Pras.new.samleMethod
12
=> 12

Resources