It's Halloween so I'll try to hide it with Python

Preface

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))
** Execution result ** (Click to open)
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.

Final product of this article

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.

Let's make it: First of all, honestly

Step 0: Confirm policy

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) ~~.

Solution using module variables

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.

Solution using closures

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.

Solution using class

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. ** **

Solution / modification using closures

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.

Step 1: Create an object that behaves like a number

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:
    ...

Step 2: Think about how to count up at the right time

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 α: Straightforward (?) Implementation

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

Let's make it: reduce the number of lines

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.

Step 1: Put the objects together

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.

Step 2: One-line MyInt class

Step 2.1: One-line processing within the method

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'))
        )
    ...

Step 2.2: Eliminate function definition statements

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.

Step 2.3: Eliminate class definition statements

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)),})

Step 3: One-line sys.setprofile (~)

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))

Step 4: One line for the whole

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))

Artifact β: Implementation that does not add extra lines

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

Summary

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. ** **

Expansion tips

List the functions that can be expanded in the future.

  1. ** Reset count **
  2. ** If you pass an argument explicitly, make the default value follow it **
  3. ** Make sure that changing the function name does not affect the default value **

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

It's Halloween so I'll try to hide it with Python
Try to operate Facebook with Python
It's Christmas, so I'll try to draw the genealogy of Jesus Christ with Cabocha
It's a hassle to write "coding: utf-8" in Python, so I'll do something with Shellscript
Try logging in to qiita with Python
3.14 π day, so try to output in Python
Try it with Word Cloud Japanese Python JupyterLab.
Try to solve the man-machine chart with Python
Try to draw a life curve with python
Try to make a "cryptanalysis" cipher with Python
Try to automatically generate Python documents with Sphinx
Try to make a dihedral group with Python
Try to detect fish with python + OpenCV2.4 (unfinished)
I was able to mock AWS-Batch with python, moto, so I will leave it
Try scraping with Python.
Try to solve the programming challenge book with python3
[First API] Try to get Qiita articles with Python
Try to make a command standby tool with python
Install selenium on Mac and try it with python
Try to operate DB with Python and visualize with d3
It's too troublesome to display Japanese with Vim's python3.
Try to automate pdf format report creation with Python
[Memorandum] python + vscode + pipenv It's common, but it was a mess with warning, so a memorandum
Try to factorial with recursion
Try to make it using GUI and PyQt in Python
Connect to BigQuery with Python
After all it is wrong to cat with python subprocess.
Try to understand Python self
When I try to push with heroku, it doesn't work
Try Python output with Haxe 3.2
Connect to Wikipedia with Python
[AWS] Try adding Python library to Layer with SAM + Lambda (Python)
Post to slack with Python 3
Try to bring up a subwindow with PyQt5 and Python
Try to automate the operation of network devices with Python
Try running Python with Try Jupyter
Switch python to 2.7 with alternatives
Write to csv with Python
Try face recognition with Python
Try to decipher the garbled attachment file name with Python
Made it possible to convert PNG to JPG with Pillow of Python
Don't write Python if you want to speed it up with Python
People who are accustomed to Android programs try multithreading with Python
Try to create a python environment with Visual Studio Code & WSL
Try to extract a character string from an image with Python3
Try to display google map and geospatial information authority map with python
Try to solve the shortest path with Python + NetworkX + social data
I made a segment tree with python, so I will introduce it
Try to get CloudWatch metrics with re: dash python data source
Try adding a wall to your IFC file with IfcOpenShell python
[Python] Try to recognize characters from images with OpenCV and pyocr
Try scraping with Python + Beautiful Soup
Python: How to use async with
Link to get started with python
[Python] Write to csv file with Python
Create folders from '01' to '12' with python
Nice to meet you with python
Try singular value decomposition with Python
Output to csv file with Python
Try to profile with ONNX Runtime
Convert list to DataFrame with python