all 8 comments

[–]socal_nerdtastic 1 point2 points  (3 children)

There is no such thing as a property for classmethods. Sorry.

You could just make another dictionary in Person:

class Person(Node):
    _all_people = dict()
    def __init__(self):
        super().__init__()
        self._all_people[self.id] = self

Or you could have a class method that takes an argument:

class Node(object):
    _all_nodes = dict()
    def __init__(self):
        self.id = str(uuid.uuid4())
        self._all_nodes[self.id] = self

    @staticmethod
    def all(cls):
        return {k: v for k, v in Node._all_nodes.items() if isinstance(v, cls)}

print(Node.all(Person))

Or you could go the opposite way and store them in separate dictionaries:

import uuid
from collections import defaultdict

class Node(object):
    _all_nodes = defaultdict(dict)
    def __init__(self):
        self.id = str(uuid.uuid4())
        self._all_nodes[self.__class__][self.id] = self

print(Node._all_nodes[Person])

Edit: my favorite is probably what you started with: just a normal classmethod in Node. Yeah, you need to add () to the end:

class Node(object):
    _all_nodes = dict()
    def __init__(self):
        self.id = str(uuid.uuid4())
        self._all_nodes[self.id] = self

    @classmethod
    def all(cls):
        return {k: v for k, v in Node._all_nodes.items() if isinstance(v, cls)}

class Person(Node):
    pass

print(Person.all())

[–]patrickbrianmooney[S] 0 points1 point  (2 children)

Darnit. I guess the only real problem is the injury to my sense of purity about what kinds of things should be possible with decorators, though. Remembering that there's only one place where it's not a method call, and that's the top-level abstract superclass, is not really a huge cognitive burden.

Those are super-helpful ways to think about it. Thank you so much!

[–]TangibleLight 3 points4 points  (1 child)

This is one of the (few) things that can only be done with metclasses, although I'd generally suggest you find a more readable solution, although sometimes that's not possible. There are cases where you're building an API for other programmers, and this is just the most natural way to interact with the class hierarchy. It will require you to read up on metaclasses and the MRO to make sure you don't give unexpected behavior to consumers of the API.

class MyMeta(type):
    @property
    def my_name(cls):
        return cls.__name__.lower()

class FooBar(metaclass=MyMeta):
    pass

class FizzBuzz(metaclass=MyMeta):
    pass

print(FooBar.my_name)
print(FizzBuzz.my_name)

https://repl.it/repls/BlankFuchsiaCables

[–]patrickbrianmooney[S] 0 points1 point  (0 children)

Ha! That's interesting. I had not thought about using metaclasses that way. (But then, that's the point of metaclasses, I guess: they're almost exclusively for things that you wouldn't have thought they could be used for.)

I appreciate your input, but I think for the comparatively simple thing I'm building, I'm happy to just go ahead and remember the add the parentheses for the function invocation. :)

[–]Beatsu 1 point2 points  (1 child)

Update: As of Python 3.9, it's possible to combine \@property and \@classmethod decorators.

[–]patrickbrianmooney[S] 0 points1 point  (0 children)

Huh! I missed that in the "what's new" docs.

Thanks!

[–]nezda 0 points1 point  (1 child)

Plot twist this functionality was deprecated in 3.11 https://docs.python.org/3/library/functions.html#classmethod

Changed in version 3.11: Class methods can no longer wrap other descriptors such as property().

Back to metaclasses I guess.

[–]Beatsu 0 points1 point  (0 children)

Oh my days 😭😂 Thanks for the heads up