How to make a dynamic method into a propery?

This all works. What I’m not sure about is how to make the method being dynamically created into a property so I can calling with out the round brackets.

class Sounds:
    def __init__(self):
        path, _ = split(argv[0])
        for basepath, dirnames, filenames in walk(path):
            if path == basepath:
                for filename in filenames:
                    name, ext = splitext(filename)
                    if ext == '.wav':
                        exec(f"def {name}(): PlaySound(sound = \'{filename}\', flags = SND_FILENAME)")
                        value = eval(name)
                        setattr(self, name, value)


Sounds().Bell()
Sounds().Siren()

I’ve been at this for awhile and I can’t figure out how to attach the property decorator to the dynamic methods

This is a pretty strange pattern, to be honest.

At first I thought you could use property(function) to get it to work but this didn’t work in my test: it gives you a property but it doesn’t get called on access. I’m guessing there’s some additional metaprogramming happening when it is used in the usual way.

The simplest solution is to not do it this way at all :sweat_smile: I’m sure you can get the same behavior without exec and eval

Properties need to belong to classes, not instances since they rely on the descriptior protocol. There is no easy way to do what you want, you will need to implement a custom __getattr__ and __setattr__ function. The correct answer is probably too not do what you are trying to do at all, although for me to be sure if that you would have to explain your goal a bit.

1 Like

I agree, this really looks like a job for __getattr__. You don’t need a property that way.

class Sounds:
    def __init__(self):
        self.sounds = {}
        path, _ = split(argv[0])
        for basepath, dirnames, filenames in walk(path):
            if path == basepath:
                for filename in filenames:
                    name, ext = splitext(filename)
                    if ext == '.wav':
                        self.sounds[name] = filename
    def __getattr__(self, attr):
        if attr in self.sounds:
            PlaySound(sound=self.sounds[attr], flags=SND_FILENAME)
            return None
        raise AttributeError

Sounds().Bell
Sounds().Siren

That said, though, I would actually NOT make these behave this way. Even though it’s possible to do attribute lookup in this way, it’s extremely surprising, and an action like “play this sound” should normally be spelled using a method call. But still, you can do it like this if you want to.

(Presumably you wouldn’t be constructing a Sounds instance afresh for every invocation, since that’s quite inefficient - doing the directory search every time.)

The class probably isn’t going to get used for anything. I was experimenting to make a class that can create methods, and properties in a class during runtime.

It would be better to use a dictionary and skip the class altogether for winsound.PlaySound.

Ahh, okay.

So, the crucial thing here is that the descriptor protocol (which is what makes properties and methods work) does NOT apply when you get something from the instance’s dictionary. It only applies when something is attached to the class. Which means you can’t easily have an instance with its own set of properties; you would have to subclass, add the properties to the class, and construct a single instance of that subclass.

It’s definitely possible. Just not often worth the hassle.

It’d probably be easier to use namedtuple to do this.

from os import scandir, getcwd
from os.path import splitext
from collections import namedtuple
from winsound import PlaySound, SND_FILENAME

sounds = {}
for entry in scandir(getcwd()):
    if entry.is_file():
        name, ext = splitext(entry.name)
        if ext == '.wav':
            sounds[name] = entry.name

Sounds = namedtuple(typename = 'Sounds', field_names = sounds.keys())(*sounds.values())
print(Sounds)
Sounds(Bell='Bell.wav', Siren='Siren.wav')
PlaySound(sound = Sounds.Bell, flags = SND_FILENAME)

Anyway …

from os import scandir, getcwd
from os.path import splitext
from winsound import PlaySound, SND_FILENAME
from collections import namedtuple


class Sounds:
    def __init__(self):
        self._sounds = {}

        for entry in scandir(path = getcwd()):
            if entry.is_file():
                name, ext = splitext(entry.name)
                if ext == '.wav':
                    exec(f'def Play{name}(): PlaySound(sound = \'{entry.name}\', flags = SND_FILENAME)')
                    setattr(self, f'Play{name}', eval(f'Play{name}'))
                    self._sounds[name] = entry.name

    def __getattr__(self, item):
        if item in self._sounds:
            return self._sounds[item]

    def sounds(self):
        return namedtuple(typename = 'Sounds', field_names = self._sounds.keys())(*self._sounds.values())


print(Sounds().sounds())
>>> Sounds(Bell='Bell.wav', Siren='Siren.wav')
print(Sounds().Bell)
>>> Bell.wav
Sounds().PlayBell()
Sounds().PlaySiren()

I agree with earlier respondents that it’s a bit strange to use properties here, but it is possible to do so and not really that difficult - if you remember that properties are descriptor objects that need to be defined on the class.

But on the other hand, properties do have the very nice feature that what looks like a simple attribute actually works like an arbitrary function (so in your cause you would not have to explicitly call “PlaySound” each time - this would be abstracted aways inside the property access).

For instance this works:

from functools import partial

def play(v, instance):
    print("Play", v)    
    # or do other stuff

class X:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(X, k, property(partial(play, v)))  
            # note: you need partial, cannot use a lambda here

That’s quite a bit simpler than all the manipulations with getattr, eval, exec, imo.
Now you get

>>> x = X(a=1, b=2, c=3)
>>> x.a
Play 1
>>> x.b
Play 2

What is a bit strange here, imo, is that the initialization of on X object sneaks in some changes to its own class definition. So you also get

>>> x = X(a=1, b=2, c=3)
>>> y = X()
>>> y.a
Play 1

In your case, you may actually see this as a desirable feature. And Python allows this kind of crazy dynamic stuff, so…

There is a pretty nice, old blog post about Python properties and descriptors that you may also find interesting: Python FAQ: Descriptors / fuzzy notepad

I mean sure, technically, but this is a terrible pattern since creating one instance affects all other instances as well. You could do something like create a new subclass for each instance and replace the __class__ attribute (or overwrite __new__ instead of __init__), but none of this is easier than just overwriting __getattr__.

I agree that in general this is an antipattern that you’d want to avoid as much as possible – it definitely has no place in library code for instance. But this might be an exception. Also, overwriting __new__ is not as simple in this case (for instance you cannot define the properties so simply inside __new__).
But then people have different opinions about what is simple or not…

Actually reminds me a little bit of Alex Martelli’s Borg-class pattern – a different way of getting the effect of singleton classes:

class Borg:
    _shared_state = {}
    def __init__(self):
        self.__dict__ = self._shared_state  # you will be assimilated, resistance is futile!

(see for instance: Avoiding the Singleton Design Pattern with the Borg Idiom - Python Cookbook [Book])

Those whole thing was a learning experience and I think named tuples would be the best way to do it. I was just curious about adding methods and properties to a class during runtime, or editing a method during runtime with inspect.getsource. I’ll check out those links you guys provided.

1 Like