Forum Archive

Python string object as callable method

Kipekeedev

I'm having a problem concatenating a string to a variable to create a method in my app. It goes something like this:

# coding: utf-8

import ui

w,h = ui.get_screen_size()
buttons = ''' Scan_View Show_View '''.split()

class OCRApp(ui.View):
    def __init__(self):
        x,y,w,h = self.bounds
        self.background_color = 'orange'
        self.present()
        for i, button in enumerate(buttons):
            button = str(button).lower()
            self.add_subview(self.make_button(button,i))

    def scan_view_action(self, sender):
        scanview = ui.load_view('scanview.pyui')
        scanview.background_color = 'red'
        scanview.present()

    def show_view_action(self,sender):
        pass 

    def make_button(self,name, i):
        button = ui.Button(title=name)
        **the_action = name
        button.action = the_action()**
        button.center =w/2, (i*60)+(button.height*2)
        self.add_subview(button)
        return button

OCRApp()

When I create a string on the variable 'the_action' and call it on 'button.action' I get an error ' TypeError: "str" object is not callable'.

How do I go about doing this correctly?

techteej

You can't set a button name as an action. You need to set each individual button action.

Kipekeedev

@techteej

Would this work?

def scan_view_action(self,sender):
    pass

def make_button(self,name):
    the_action = name + '_action'
    button.action = the_action

Or is there any convenient way to do this to keep it DRY?

Kipekeedev

Solved it!

the_action = getattr(self, name + '_action')
button.action = the_action
ccc

The code above calls .add_subview() twice for each button which will put four buttons on the screen instead of just two. A minimal implementation:

# coding: utf-8

import ui, console

w, h = ui.get_screen_size()
buttons = 'Scan_View Show_View'.split()

class OCRApp(ui.View):
    def __init__(self):
        self.background_color = 'orange'
        self.present()
        for i, button in enumerate(buttons):
            self.add_subview(self.make_button(button.lower(), i))

    def scan_view_action(self, sender):
        console.hud_alert('scan')

    def show_view_action(self, sender):
        console.hud_alert('show')

    def make_button(self, name, i):
        button = ui.Button(title=name)
        button.action = getattr(self, name + '_action')
        button.center = w / 2, i * 60 + button.height * 2
        return button

OCRApp()
Kipekeedev

Thanks @ccc

Kipekeedev

I know this is out of topic but I'm having trouble running this code. It freezes my app:

@ui.in_background
    def scan_document(self, sender):

        img = photos.capture_image()
        console.show_activity()
        if not img:
            return
        with io.BytesIO() as bIO:
            img.save(bIO, 'PNG')
            imgOut = ui.Image.from_data

I don't know if you guys know a lot about the photos module but I can't find any good documentation.

@ccc do you have a github post on it?

ccc

http://omz-software.com/pythonista/docs/ios/photos.html

https://github.com/Pythonista-Tools/Pythonista-Tools/blob/master/Graphics%20and%20Imaging.md

JonB

do you have another in_background that is running? i tried your code standalone, and it works ok for me on the 1.6 beta.

I do recall similar porblems on the 1.5, which were solved by not using in_background, but then having the logic that shows the capture phoo dialog called in a ui.delay.

Phuket2

I will ask the obvious , do you really need to do Background processing ? But I understand, you should be still be able to background process if possible. But sometimes linear processing is reasonable given the task. If I am off track, ignore my comments

Kipekeedev

@Phuket2

If I remove the decorator
```
@ui.in_background

I get an error:

TypeError: Cannot show camera from main UI thread

```

Phuket2

@Kipekeedev, ok I should not be commenting because it's outside my comprehension. But, if you look at what the decorator is doing might give you a better insight to what is happening. I am not trying to be smart, but I have fell into this trap before. You have a decorator called ui.in_background somehow it has some special and magical meaning. But it doesn't. If you look at the root of what the decorator is doing ( in this case it's about threads). Again I am talking above what I know exactly, but I think my idea is correct. Hope what I have said is not misleading.

JonB

The built in dialogs (like console.input_alert, raw_input, camera input dialogs, etc) need to be run in a background thread, though in 1.5 at least, there were some cases where in_background didn't work... possibly because of the fact that everything which uses in_background shares the same queue. I think there was some tuning of priorities in 1.6 (your code worked okay for me, but perhaps we need to see the entire app)

Using ui.delay is a simple way to spawn a new independent thread, without having to learn about the threading module. You would do something like

def scan_document(self, sender):
    def show():  
        img = photos.capture_image()
        console.show_activity()
        if not img:
            return
        with io.BytesIO() as bIO:
            img.save(bIO, 'PNG')
            imgOut = ui.Image.from_data
            # actually DO something with the image
  ui.delay(show,0.1)

if that still doesn't work, try first closing the view, before the ui.delay, then increase the delay time to a second or so, to give the view time to minimize. at the end of show you could represent the top level view. Again, i did not have to do any of this in 1.6, but sometimes such things were needed in 1.5.