The third Halloween story.
--First: Because it's Halloween, let's make Python loose --Second: Because it's Halloween, I'll split Python into eight: point_left_tone2: ** Confident work **
For example, suppose you have this code. It's a common Fizzbuzz code.
Fizzbuzz without any change
def fizzbuzz(n):
if n % 15 == 0:
return 'fizzbuzz'
if n % 3 == 0:
return 'fizz'
if n % 5 == 0:
return 'buzz'
return str(n)
for i in range(20):
print(fizzbuzz(i+1))
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz
However, ʻi + 1 of
fizzbuzz (i + 1) `looks ugly.
Since you're stuck in a for statement, don't you want me to read the arguments and guess? [^ 1] I want it to work the same even if I call it as follows.
for _ in range(20):
print(fizzbuzz())
Challenge the spirited away of the actual argument.
[^ 1]: I don't think.
I made something like this.
Final product
#↓ Don't worry about the right from here
def fizzbuzz(n=(lambda mi: (__import__('sys').setprofile(mi.hook), mi)[-1])(type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})(1))):
if n % 15 == 0:
return 'fizzbuzz'
if n % 3 == 0:
return 'fizz'
if n % 5 == 0:
return 'buzz'
return str(n)
for _ in range(20):
print(fizzbuzz())
** Execution result ** ⇒ Wandbox
Of course, it can be executed normally, and the execution result is the same as before the modification. In this article, I will explain how this code was completed.
If you just omit the actual arguments, there are many ways to do it. In this section, I would like to clarify the goals by writing down each straightforward solution and its difficulties ~~ (or difficult habits) ~~.
default_n = 1
def fizzbuzz(n=None):
global default_n
if n is None:
n = default_n
default_n += 1
if n % 15 == 0:
...
A bad global declaration has emerged, and the implementation of the function must be modified. It is out of the question.
def make_fizzbuzz(n_start=1):
default_n = n_start
def _fizzbuzz(n=None):
nonlocal default_n
if n is None:
n = default_n
default_n += 1
if n % 15 == 0:
...
return _fizzbuzz
fizzbuzz = make_fizzbuzz()
There are some advantages over the method using the previous module variable.
--The initial value of n (n_start) can be specified when the fizzbuzz function is generated. --The scope of default_n is confined inside the function. Many times better than module variables.
However, the implementation of the fizzbuzz function needs to be significantly modified.
class CountedUpInt:
def __init__(self, start_n=1):
self._n = start_n
def __int__(self):
n = self._n
self._n += 1
return n
def fizzbuzz(n=CountedUpInt()):
n = int(n)
if n % 15 == 0:
...
A straightforward solution. Easy to understand [^ 2] and easy to repair.
[^ 2]: However, you need to understand how to handle Python's default arguments. The default ** value ** is evaluated only once when the function is defined. Well, even with the default ** expression **, you can build similar code by using class variables.
However, this method still requires you to modify the inside of the implementation of the fizzbuzz function. ** The goal is to realize spirited away by just devising the default arguments without touching the code part of the fizzbuzz function. ** **
If you make good use of higher-order functions, you can avoid touching the implementation of the fizzbuzz function. Readability is also improved when using a decorator together.
def countup_wrapper(n_start):
default_n = n_start
def _inner1(func):
def _inner2(n=None):
nonlocal default_n
if n is None:
n = default_n
default_n += 1
return func(n)
return _inner2
return _inner1
@countup_wrapper(1)
def fizzbuzz(n):
...
I came up with it after I finished writing most of the articles. ~~ ** Is this all right? ** ~~ Since the article I wrote will be wasted, I would like you to take the nonlocal allergy as a shield and ignore it.
It is a binding that modifies only the default argument.
As long as you don't touch the code part, the default value must be able to be treated in the same way as a numerical value. For example, in order to realize the modulo operation, the class should be organized as follows. [^ 3]
[^ 3]: If you want to put the MyInt object in the right operand of modulo, you also need to implement the __rmod__
method.
class MyInt:
def __init__(self, n):
self._n = n
def __mod__(self, other):
return self._n % other
def __str__(self):
return str(self._n)
print(16 % 6) # => 4
print(MyInt(16) % 6) # => 4
__mod__
is a special method responsible for modulo arithmetic, and ʻa% b is equivalent to ʻa .__ mod__ (b)
.
** Reference **: [Python Language Reference »3. Data Model» 3.3.8. Emulate a Number Type](https://docs.python.org/ja/3/reference/datamodel.html#emulating- numeric-types)
At this point, you can write code like this: However, since the count-up process has not been implemented, the same number will naturally be repeatedly determined.
def fizzbuzz(n=MyInt(1)):
if n % 15 == 0:
...
If you are not involved in the implementation of the fizzbuzz function, you have no choice but to hook the function call / escape. Here, let's realize the hook by using the sys.setprofile function ~~ abuse ~~ ** diversion **. I think.
class MyInt:
...
def succ(self):
self._n += 1
...
import sys
myInt = MyInt(1)
def _hook(frame, event, arg):
# frame.f_code.co_name Function name for the event
# event 'call'Or'return'
if (frame.f_code.co_name, event) == ('fizzbuzz', 'return'):
myInt.succ()
sys.setprofile(_hook)
#
def fizzbuzz(n=myInt):
if n % 15 == 0:
...
** Reference **: Python Standard Library »inspect --Types and Members
At this point, the above-mentioned purpose "Don't touch the code part of the fizzbuzz function" has already been achieved.
Deliverable α
class MyInt:
def __init__(self, n):
self._n = n
def succ(self):
self._n += 1
def __mod__(self, other):
return self._n % other
def __str__(self):
return str(self._n)
import sys
myInt = MyInt(1)
def _hook(frame, event, arg):
if (frame.f_code.co_name, event) == ('fizzbuzz', 'return'):
myInt.succ()
sys.setprofile(_hook)
#
def fizzbuzz(n=myInt):
if n % 15 == 0:
return 'fizzbuzz'
if n % 3 == 0:
return 'fizz'
if n % 5 == 0:
return 'buzz'
return str(n)
for _ in range(20):
print(fizzbuzz())
** Execution result ** ⇒ Wandbox
However, there are too many descriptions other than the fizzbuzz function in the previous product α. To emphasize that fizzbuzz is the main purpose, ** I will write it with as few lines as possible ** [^ 4].
[^ 4]: Don't say that the art style is narrow. The figure is too starry and hurt.
In order to make the later description a little easier, I will summarize the objects to some extent. For example, the _hook function can be incorporated as a method of the MyInt class.
class MyInt:
...
def hook(self, frame, event, arg):
if (frame.f_code.co_name, event) == ('fizzbuzz', 'return'):
self._n += 1
myInt = MyInt(1)
sys.setprofile(myInt.hook)
The succ method has been removed for the convenience of later modifications.
Implement the method with only one expression statement or return statement.
--The assignment statement can be replaced by setattr. [^ 5] , </ sup> [^ 6]
--The if statement can be simply replaced by the conditional operator or (value 1, value 2) [condition]
, but here I would like to take advantage of the fact that True / False are 1/0 respectively. ..
[^ 5]: Python 3.8 introduced the assignment operator (commonly known as the walrus operator), but there seems to be a limit to the context in which it can be used. [^ 6]: I want to write stateless processing without using it as much as possible. Is the feeling of losing if I use setattr easily only for me?
class MyInt:
def __init__(self, n):
setattr(self, '_n', n)
def hook(self, frame, event, arg):
setattr(self, '_n',
#Self when the condition is true._n +Equivalent to 1, if false, self._n +Equivalent to 0.
self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return'))
)
...
Redefine each method using a lambda expression.
class MyInt:
__init__ = (lambda self, n:
setattr(self, '_n', n)
)
hook = (lambda self, frame, event, _:
setattr(
self, '_n',
self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return'))
)
)
__mod__ = (lambda self, other:
self._n % other
)
__str__ = (lambda self:
str(self._n)
)
Since a method is a class variable after all, its replacement is surprisingly easy.
You can use the type function to create a class object in an expression. Class attributes are passed together in a dictionary.
MyInt = type('MyInt', (object, ), {
'__init__': (lambda self, n:
setattr(self, '_n', n)
),
'hook': (lambda self, frame, event, _:
setattr(
self, '_n',
self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return'))
)
),
'__mod__': (lambda self, other:
self._n % other
),
'__str__': (lambda self:
str(self._n)
),
})
At this point, the class definition is one-liner.
MyInt = type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})
You can use the __import__
function instead of the import statement and write:
__import__('sys').setprofile(MyInt(1).hook)
In fact, this code is incomplete. The MyInt instance I created has been dropped and can no longer be recovered [^ 7].
[^ 7]: Strictly speaking, it's a lie. However, collecting with myInt = sys.getprofile () .__ self __
is annoying.
It is common practice to make good use of lambda expressions as follows.
myInt = (lambda mi:
#Create a tuple and return its last element.
(__import__('sys').setprofile(mi.hook), mi)[-1]
#Create an object only once.
)(MyInt(1))
Just embed the above type object directly in the MyInt part.
myInt = (lambda mi:
(__import__('sys').setprofile(mi.hook), mi)[-1]
)(type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})(1))
Use the above object for the default value myInt of the argument n to get the final product mentioned above.
Deliverable β(Final product)
def fizzbuzz(n=(lambda mi: (__import__('sys').setprofile(mi.hook), mi)[-1])(type('MyInt', (object, ), {'__init__': (lambda self, n: setattr(self, '_n', n)), 'hook': (lambda self, frame, event, _: setattr(self, '_n', self._n + ((frame.f_code.co_name, event) == ('fizzbuzz', 'return')))), '__mod__': (lambda self, other: self._n % other), '__str__': (lambda self: str(self._n)),})(1))):
if n % 15 == 0:
return 'fizzbuzz'
if n % 3 == 0:
return 'fizz'
if n % 5 == 0:
return 'buzz'
return str(n)
for _ in range(20):
print(fizzbuzz())
** Execution result ** ⇒ Wandbox
As mentioned above, I was able to write the code without the actual arguments. If you can omit the obvious arguments when writing code ... Wouldn't you like to do it by any means?
** I don't think. ** **
List the functions that can be expanded in the future.
Image of extension 1
print(fizzbuzz()) # => 1
print(fizzbuzz()) # => 2
print(fizzbuzz()) # => fizz
fizzbuzz.reset(10)
print(fizzbuzz()) # => buzz
print(fizzbuzz()) # => 11
Image of extension 2
print(fizzbuzz()) # => 1
print(fizzbuzz()) # => 2
print(fizzbuzz()) # => fizz
print(fizzbuzz(10)) # => buzz
print(fizzbuzz()) # => 11
Extension 3 has been provisionally implemented at this stage. I'm also interested in 1 and 2, so I'll write about them soon. I don't know if it will be an article.
Recommended Posts