so I'm working on a class for a vector which I want to hash like a tuple, and to do so, a vector instance needs acess to it's properties.
here's the constructor for the performative properties I'm using, and the vector class:

def newProp():
    newdict = {}
    getter = newdict.get
    setter = newdict.__setitem__
    def wrapper(vec,val):
        if val is None: return
        vtype = val.__class__
        if vtype is str: setter(vec, float(val) ); return
        if numtype(vtype): setter(vec,val)
    return property(getter, wrapper)

class vector(object):
    __slots__ = []
    X = newProp()
    Y = newProp()
    Z = newProp()
    W = newProp()

    def __init__(vec,*other): # other is for special initialization
        vec.X = vec.Y = vec.Z = vec.W = None

    def __hash__(vec): return hash( (vec.X, vec.Y, vec.Z, vec.W) )

now there's alot to be questioned, particularly because I don't know everything about how Python works...
but this is just the most performative approach I could think of for an automated vector class.

the problem I'm getting is hash(vector()) leads to a recursion error since dict.get calls vec.hash for each property.

I'm looking for a workaround that's just as performative or better.
thanks. :)

Recommended Answers

All 11 Replies

Strange code and strange specifications as usual, but well ... Two points:

  1. Why do you want the vector to hash as a tuple ? What's the use of this ? There must be a good reason as this breaks your code.
  2. There is a serious drawback in your properties: the vector instances inserted in the dictionaries will never be garbage collected. The properties' dictionaries create memory leak.

Here is a version that addresses both issues. I aggregate a new small object named _key to each vector and this object is hashed in the property dictionary instead of the vector instance. Second, I use a WeakKeyDictionary, so that vectors can again be garbage collected.

from weakref import WeakKeyDictionary

def newProp():
    newdict = WeakKeyDictionary()
    def getter(vec):
        return newdict.get(vec._key)
    def setter(vec, val):
        newdict[vec._key] = val
    def wrapper(vec,val):
        if val is None: return
        vtype = val.__class__
        if vtype is str: setter(vec, float(val) ); return
        if numtype(vtype): setter(vec,val)
    return property(getter, wrapper)

class Key(object):
    __slots__ = ('__weakref__',)

class vector(object):
    __slots__ = ('_key',)
    X = newProp()
    Y = newProp()
    Z = newProp()
    W = newProp()
    def __init__(vec, *other): # other is for special initialization
        vec._key = Key()
        vec.X = vec.Y = vec.Z = vec.W = None
    def __hash__(vec):
        return hash( (vec.X, vec.Y, vec.Z, vec.W) )

def main():
    v = vector()
    print(hash(v))

if __name__ == '__main__':
    main()

I'm sure you won't like this, but you can still change your specifications and your algorithm.

commented: Classy. +12

I know there's no GC going on, that's what I want ;)
I need to track each UGEObject instance for debug purposing in SIDE, since SIDE is directly integrated with the API.

so with that being said, the hash is needed to prevent creating duplicate Objects.
but it also has it's purposes for the automation going on between conversion and data management within the backend.

so anyways, weakref is something I have yet to understand...
can you explain to me how what you did works :/

I just realized, the reason I gave is not the reason I intended hash to be used for #facepalm

in my collections, when I said "creating duplicate Objects" what I meant was comparison via hash.

sure tuples use a similar method with __new__ to return the similar instance, but this isn't meant in that way.

My code works because newdict is no longer a dictionary vector --> value. It is a dictionary vector._key --> value instead. It means that only the ._key member is hashed when properties are being accessed, not the vector object.

If you don't use weakrefs, you don't need a Key class with a __weakref__ attribute. You can simply initialize vec._key = object(), but the keys will never leave the dictionary.

A WeakKeyDictionary doesn't prevent its keys to be garbage collected. When this happens, they seem to vanish magically from the dictionary. It allows to store additional information about objects without interfering with the objects' life cycle.

ah alright, yeah you were right, I don't like it XD

also, I see you've decreased performance by wrapping get and setitem with an extra function.
to add, you have a dot operator for each get.
the dot operator is 200ns slower for dict().get() vs just plain get()

the get in your code is also slow, but not quite the best that WeakKeyDictionary can do:

self.data.get(ref(key), default)

could be reduced to

dataget(ref(key), default)

if get was defined in init.

so yeah, your code is not nearly as performative as mine (excluding the borkage of course).
however, I think I understand a little about what you're doing and may be able to build a performative version. ;)

performance is critical here because the attributes here will typically be used in a large array of instances.

I think I've found my solution with credit to your code ;)
vec._key is basically a separate object where the data is accessed from, so I took from that and did something better. ;)

instead of using a weak dictionary, I'm using member descriptors and supplying their get and set methods to the property in the subclass.
and yes, the property name can be the same as the descriptor name.

the benefit of this is the property is just about as performative as the descriptor.

it's important to note though that test must be inherited for the descriptors to function with test2.

In your example, I don't see why you need A = property(...) when you can directly use i.a.

because think of this in terms of the vector example you provided....
in your example, you needed to store the instance of the superclass you were using to access it's attributes.

what I'm doing doesn't create the separate instance in the first place,
but I need access to the descriptors beforehand because the property setters are wrapped with a validation function.

to add, accessing from an instance adds an extra dot operator, which as I've mentioned earlier that the dot operator is slow.
it's only faster when applied to C-based python objects, but even then it's still slow.

gotta pay attention to the interpreter steps ;)

heh, so after finally making the edits to test this sucker, it turns out that my above test wasn't advanced enough to deal with everything appropriately.

I'm running into a slots issue which seems to be pretty common.
TypeError: multiple bases have instance lay-out conflict

I'm kinda stumped as to what I could do to fix it...

EDIT: here's my vector code if you might be able to help
https://photos.app.goo.gl/4QWNSoiGGG6ubD792

EDIT2:
__public__ is a var for the UGEObject metaclass that creates __slots__ for attributes not in the namespace.

__disabled__ prevents the creation of proxy attributes.
these used to be Parent, Child, Next, and Prev
but I've separated those into another class, so all that remains is Name and Index.

fixed my vector code, completely removed dict handling of both the attributes, and the proxy link infrastructure.

here's the working code:

https://cdn.discordapp.com/attachments/161204326218465280/358566142270373889/working_vector_code.png

as well as the debug result showing it works:

https://cdn.discordapp.com/attachments/98580359909773312/358440129150582784/I_feel_good.png

Bone attributes Locaction, Rotation, and Scale are all vector instances.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.