Related
I defined a class named Experiment for the results of some lab experiments I am conducting. The idea was to create a sort of database: if I add an experiment, this will be pickled to a db before at exit and reloaded (and added to the class registry) at startup.
My class definition is:
class IterRegistry(type):
def __iter__(cls):
return iter(cls._registry)
class Experiment(metaclass=IterRegistry):
_registry = []
counter = 0
def __init__(self, name, pathprotocol, protocol_struct, pathresult, wallA, wallB, wallC):
hashdat = fn.hashfile(pathresult)
hashpro = fn.hashfile(pathprotocol)
chk = fn.checkhash(hashdat)
if chk:
raise RuntimeError("The same experiment has already been added")
self._registry.append(self)
self.name = name
[...]
While fn.checkhash is a function that checks the hashes of the files containing the results:
def checkhash(hashdat):
for exp in cl.Experiment:
if exp.hashdat == hashdat:
return exp
return False
So that if I add a previously added experiment, this won't be overwritten.
Is it possible to somehow return the existing instance if already existant instead of raising an error? (I know in __init__ block it is not possible)
You can use __new__ if you want to customize the creation instead of just initializing in newly created object:
class Experiment(metaclass=IterRegistry):
_registry = []
counter = 0
def __new__(cls, name, pathprotocol, protocol_struct, pathresult, wallA, wallB, wallC):
hashdat = fn.hashfile(pathresult)
hashpro = fn.hashfile(pathprotocol)
chk = fn.checkhash(hashdat)
if chk: # already added, just return previous instance
return chk
self = object.__new__(cls) # create a new uninitialized instance
self._registry.append(self) # register and initialize it
self.name = name
[...]
return self # return the new registered instance
Try to do it this way (very simplified example):
class A:
registry = {}
def __init__(self, x):
self.x = x
#classmethod
def create_item(cls, x):
try:
return cls.registry[x]
except KeyError:
new_item = cls(x)
cls.registry[x] = new_item
return new_item
A.create_item(1)
A.create_item(2)
A.create_item(2) # doesn't add new item, but returns already existing one
After four years of the question, I got here and Serge Ballesta's answer helped me. I created this example with an easier syntax.
If base is None, it will always return the first object created.
class MyClass:
instances = []
def __new__(cls, base=None):
if len(MyClass.instances) == 0:
self = object.__new__(cls)
MyClass.instances.append(self)
if base is None:
return MyClass.instances[0]
else:
self = object.__new__(cls)
MyClass.instances.append(self)
# self.__init__(base)
return self
def __init__(self, base=None):
print("Received base = %s " % str(base))
print("Number of instances = %d" % len(self.instances))
self.base = base
R1 = MyClass("apple")
R2 = MyClass()
R3 = MyClass("banana")
R4 = MyClass()
R5 = MyClass("apple")
print(id(R1), R1.base)
print(id(R2), R2.base)
print(id(R3), R3.base)
print(id(R4), R4.base)
print(id(R5), R5.base)
print("R2 == R4 ? %s" % (R2 == R4))
print("R1 == R5 ? %s" % (R1 == R5))
It gives us the result
Received base = apple
Number of instances = 2
Received base = None
Number of instances = 2
Received base = banana
Number of instances = 3
Received base = None
Number of instances = 3
Received base = apple
Number of instances = 4
2167043940208 apple
2167043940256 None
2167043939968 banana
2167043940256 None
2167043939872 apple
R2 == R4 ? True
R1 == R5 ? False
Is nice to know that __init__ will be always called before the return of the __new__, even if you don't call it (in commented part) or you return an object that already exists.
I wrote a simple Proxy class in python3, but I have a problem with "was_called" function
class Proxy:
last_invoked = ""
calls = {}
def __init__(self, obj):
self._obj = obj
def __getattr__(self, item):
attrs = dir(self._obj)
if item in attrs:
Proxy.last_invoked = item
if item in Proxy.calls.keys():
Proxy.calls[item] += 1
else:
Proxy.calls[item] = 1
if item in Proxy.calls.keys():
Proxy.calls[item] += 1
else:
Proxy.calls[item] = 1
return getattr(self._obj, item)
else:
raise Exception('No Such Method')
def last_invoked_method(self):
if Proxy.last_invoked == "":
raise Exception('No Method Is Invoked')
else:
return Proxy.last_invoked
def count_of_calls(self, method_name):
if method_name in Proxy.calls.keys():
return Proxy.calls[method_name]
return 0
def was_called(self, method_name):
if method_name in Proxy.calls.keys():
if Proxy.calls[method_name] > 0: return True
return False
class Radio():
def __init__(self):
self._channel = None
self.is_on = False
self.volume = 0
def get_channel(self):
return self._channel
def set_channel(self, value):
self._channel = value
def power(self):
self.is_on = not self.is_on
radio = Radio()
radio_proxy = Proxy(radio)
radio.number = 3
radio_proxy.number = 3
radio_proxy.power()
print(radio_proxy.was_called("number"))
print(radio_proxy.was_called("power"))
"was_called" function is work for functions and attributes that is in radio at first such as "power", but it's not work for new attributes that we add such as "number".
I expect for both print "True", because both of "power" and "number" is called. but first print return False!
What do you suggest?
def Proxy(class_type):
class ProxyClass(class_type):
def __init__(self, *args, **kwargs):
# Set your _calls and _last_invoked here, so that they are not class attributes (and are instead instance attributes).
self._calls = {}
self._last_invoked = ""
# Pass the arguments back to the class_type (in our case Radio) to initialize the class.
super().__init__(*args, **kwargs)
def __getattribute__(self, item):
# We must do this prelimary check before continuing on to the elif statement.
# This is since _calls and _last_invoked is grabbed when self._last_invoked/self._calls is called below.
if item in ("_calls", "_last_invoked"):
return super(ProxyClass, self).__getattribute__(item)
elif not item.startswith("_"):
self._last_invoked = item
self._calls[item] = 1 if item not in self._calls.keys() else self._calls[item] + 1
return super(ProxyClass, self).__getattribute__(item)
def __setattr__(self, item, val):
# Wait until _calls is initialized before trying to set anything.
# Only set items that do not start with _
if not item == "_calls" and not item.startswith("_"):
self._calls[item] = 0
super(ProxyClass, self).__setattr__(item, val)
def last_invoked_method(self):
if self._last_invoked == "":
raise Exception('No Method Is Invoked')
else:
return self._last_invoked
def count_of_calls(self, method_name):
return self._calls[method_name] if method_name in self._calls.keys() else 0
def was_called(self, method_name):
return True if method_name in self._calls.keys() and self._calls[method_name] > 0 else False
return ProxyClass
#Proxy
class Radio():
def __init__(self):
self._channel = None
self.is_on = False
self.volume = 0
def get_channel(self):
return self._channel
def set_channel(self, value):
self._channel = value
def power(self):
self.is_on = not self.is_on
radio = Proxy(Radio)()
radio.number = 3 # Notice that we are only setting the digit here.
radio.power()
print(radio._calls)
print(radio.number) # Notice that this when we are actually calling it.
print(radio._calls)
outputs:
{'is_on': 0, 'volume': 0, 'number': 0, 'power': 1}
3
{'is_on': 0, 'volume': 0, 'number': 1, 'power': 1}
A few modifications here and there, but you should be able to see the bigger idea by reading through the code. From here you should be able to modify the code to your liking. Also note that any variable that starts with _ is automatically removed from the _calls dictionary.
If you rather not use the decorator #Proxy, you may initialize your Radio class (as a proxy) like so:
# Second parentheses is where your Radio args go in.
# Since Radio does not take any args, we leave it empty.
radio_proxy = Proxy(Radio)()
Also, make sure to understand the difference between class attributes, and instance attributes.
Edit:
class Test:
def __init__(self, var):
self.var = var
self.dictionary = {}
def __getattribute__(self, item):
print("we are GETTING the following item:", item)
# If we don't do this, you end up in an infinite loop in which Python is
# trying to get the `dictionary` class to do `self.dictionary['dictionary'] = ...`
if item == "dictionary":
super(Test, self).__getattribute__(item)
else:
self.dictionary[item] = "Now we can use this!"
return super(Test, self).__getattribute__(item)
def __setattr__(self, item, key):
print("we are SETTING the following item:", item)
super(Test, self).__setattr__(item, key)
Notice:
test = Test(4)
outputs:
we are SETTING the following item: var
we are SETTING the following item: dictionary
then following it:
test.var
outputs:
we are GETTING the following item: var
we are GETTING the following item: dictionary
Thrift 0.9.2 now allows for recursive structs that can contain instances of themselves. For example this thrift definition:
namespace py foo
struct Foo {
1: optional string member
2: optional Foo fooData
}
Produces the following code:
#
# Autogenerated by Thrift Compiler (0.9.2)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
# options string: py
#
from thrift.Thrift import TType, TMessageType, TException, TApplicationException
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol, TProtocol
try:
from thrift.protocol import fastbinary
except:
fastbinary = None
class Foo:
"""
Attributes:
- member
- fooData
"""
thrift_spec = (
None, # 0
(1, TType.STRING, 'member', None, None, ), # 1
(2, TType.STRUCT, 'fooData', (Foo, Foo.thrift_spec), None, ), # 2
)
def __init__(self, member=None, fooData=None,):
self.member = member
self.fooData = fooData
def read(self, iprot):
if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
return
iprot.readStructBegin()
while True:
(fname, ftype, fid) = iprot.readFieldBegin()
if ftype == TType.STOP:
break
if fid == 1:
if ftype == TType.STRING:
self.member = iprot.readString();
else:
iprot.skip(ftype)
elif fid == 2:
if ftype == TType.STRUCT:
self.fooData = Foo()
self.fooData.read(iprot)
else:
iprot.skip(ftype)
else:
iprot.skip(ftype)
iprot.readFieldEnd()
iprot.readStructEnd()
def write(self, oprot):
if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
return
oprot.writeStructBegin('Foo')
if self.member is not None:
oprot.writeFieldBegin('member', TType.STRING, 1)
oprot.writeString(self.member)
oprot.writeFieldEnd()
if self.fooData is not None:
oprot.writeFieldBegin('fooData', TType.STRUCT, 2)
self.fooData.write(oprot)
oprot.writeFieldEnd()
oprot.writeFieldStop()
oprot.writeStructEnd()
def validate(self):
return
def __hash__(self):
value = 17
value = (value * 31) ^ hash(self.member)
value = (value * 31) ^ hash(self.fooData)
return value
def __repr__(self):
L = ['%s=%r' % (key, value)
for key, value in self.__dict__.iteritems()]
return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
def __ne__(self, other):
return not (self == other)
The problem with this code is in the class variable thrift_spec:
thrift_spec = (
None, # 0
(1, TType.STRING, 'member', None, None, ), # 1
(2, TType.STRUCT, 'fooData', (Foo, Foo.thrift_spec), None, ), # 2
)
There are two problems here:
The class def tries to reference Foo before it is fully defined (and will produce NameError: name 'Foo' is not defined) when instantiated.
thrift_spec tries to contain a reference to itself which is a bizarre thing to do in python (self-referential tuple?).
This tuple is used by a package in thrift called fastbinary, a C-module that serializes and deserializes thrift objects natively. The tuple is used to pass along type information to the C-module.
The question I have is how best to solve these two problems. For #1 I can move the instantiation of thrift_spec after the class definition or could move it into an #property. For #2 I'm really not sure what to do. Is there some way to get a reference to the tuple object and pass that to the C-module?
Note this is an open bug in thrift that I was hoping to help solve:
https://issues.apache.org/jira/browse/THRIFT-2642
I have an object (Person) that has multiple subobjects (Pet, Residence) as properties. I want to be able to dynamically set the properties of these subobjects like so:
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
class Residence(object):
def __init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
if __name__=='__main__':
p=Person()
setattr(p,'pet.name','Sparky')
setattr(p,'residence.type','Apartment')
print p.__dict__
Currently I get the wrong output: {'pet': <__main__.Pet object at 0x10c5ec050>, 'residence': <__main__.Residence object at 0x10c5ec0d0>, 'pet.name': 'Sparky', 'residence.type': 'Apartment'}
As you can see, instead of setting the name attribute on the Pet subobject of the Person, a new attribute pet.name is created on the Person.
I cannot specify person.pet to setattr() because different sub-objects will be set by the same method, which parses some text and fills in the object attributes if/when a relevant key is found.
Is there a easy/builtin way to accomplish this?
Or perhaps I need to write a recursive function to parse the string and call getattr() multiple times until the necessary subobject is found and then call setattr() on that found subobject?
You could use functools.reduce:
import functools
def rsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
# using wonder's beautiful simplification: https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
return getattr(obj, attr, *args)
return functools.reduce(_getattr, [obj] + attr.split('.'))
rgetattr and rsetattr are drop-in replacements for getattr and setattr,
which can also handle dotted attr strings.
import functools
class Person(object):
def __init__(self):
self.pet = Pet()
self.residence = Residence()
class Pet(object):
def __init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
class Residence(object):
def __init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
def rsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
def rgetattr(obj, attr, *args):
def _getattr(obj, attr):
return getattr(obj, attr, *args)
return functools.reduce(_getattr, [obj] + attr.split('.'))
if __name__=='__main__':
p = Person()
print(rgetattr(p, 'pet.favorite.color', 'calico'))
# 'calico'
try:
# Without a default argument, `rgetattr`, like `getattr`, raises
# AttributeError when the dotted attribute is missing
print(rgetattr(p, 'pet.favorite.color'))
except AttributeError as err:
print(err)
# 'Pet' object has no attribute 'favorite'
rsetattr(p, 'pet.name', 'Sparky')
rsetattr(p, 'residence.type', 'Apartment')
print(p.__dict__)
print(p.pet.name)
# Sparky
print(p.residence.type)
# Apartment
For an out of the box solution, you can use operator.attrgetter:
from operator import attrgetter
attrgetter(dotted_path)(obj)
For one parent and one child:
if __name__=='__main__':
p = Person()
parent, child = 'pet.name'.split('.')
setattr(getattr(p, parent), child, 'Sparky')
parent, child = 'residence.type'.split('.')
setattr(getattr(p, parent), child, 'Sparky')
print p.__dict__
This is simpler than the other answers for this particular use case.
unutbu's answer (https://stackoverflow.com/a/31174427/2683842) has a "bug". After getattr() fails and is replaced by default, it continues calling getattr on default.
Example: rgetattr(object(), "nothing.imag", 1) should equal 1 in my opinion, but it returns 0:
getattr(object(), 'nothing', 1) == 1.
getattr(1, 'imag', 1) == 0 (since 1 is real and has no complex component).
Solution
I modified rgetattr to return default at the first missing attribute:
import functools
DELIMITER = "."
def rgetattr(obj, path: str, *default):
"""
:param obj: Object
:param path: 'attr1.attr2.etc'
:param default: Optional default value, at any point in the path
:return: obj.attr1.attr2.etc
"""
attrs = path.split(DELIMITER)
try:
return functools.reduce(getattr, attrs, obj)
except AttributeError:
if default:
return default[0]
raise
This should be a
def getNestedAttr(obj,nestedParam):
next = obj
for p in nestedParam.split('.'):
next = getattr(next,p)
return next
class Issue : pass
issue = Issue()
issue.status = Issue()
issue.status.name = "Hello"
getattr(issue,'status.name')
'''
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Issue' object has no attribute 'status.name'
'''
getNestedAttr(issue,'status.name')
#'Hello'
simple solution
I made a simple version based on ubntu's answer called magicattr that also works on attrs, lists, and dicts by parsing and walking the ast.
For example, with this class:
class Person:
settings = {
'autosave': True,
'style': {
'height': 30,
'width': 200
},
'themes': ['light', 'dark']
}
def __init__(self, name, age, friends):
self.name = name
self.age = age
self.friends = friends
bob = Person(name="Bob", age=31, friends=[])
jill = Person(name="Jill", age=29, friends=[bob])
jack = Person(name="Jack", age=28, friends=[bob, jill])
You can do this
# Nothing new
assert magicattr.get(bob, 'age') == 31
# Lists
assert magicattr.get(jill, 'friends[0].name') == 'Bob'
assert magicattr.get(jack, 'friends[-1].age') == 29
# Dict lookups
assert magicattr.get(jack, 'settings["style"]["width"]') == 200
# Combination of lookups
assert magicattr.get(jack, 'settings["themes"][-2]') == 'light'
assert magicattr.get(jack, 'friends[-1].settings["themes"][1]') == 'dark'
# Setattr
magicattr.set(bob, 'settings["style"]["width"]', 400)
assert magicattr.get(bob, 'settings["style"]["width"]') == 400
# Nested objects
magicattr.set(bob, 'friends', [jack, jill])
assert magicattr.get(jack, 'friends[0].friends[0]') == jack
magicattr.set(jill, 'friends[0].age', 32)
assert bob.age == 32
It also won't let you/someone call functions or assign a value since it doesn't use eval or allow Assign/Call nodes.
with pytest.raises(ValueError) as e:
magicattr.get(bob, 'friends = [1,1]')
# Nice try, function calls are not allowed
with pytest.raises(ValueError):
magicattr.get(bob, 'friends.pop(0)')
And a easy to understand three-liner based on jimbo1qaz's answer, reduced to the very limit:
def rgetattr(obj, path, default):
try:
return functools.reduce(getattr, path.split(), obj)
except AttributeError:
return default
Usage:
>>> class O(object):
... pass
... o = O()
... o.first = O()
... o.first.second = O()
... o.first.second.third = 42
... rgetattr(o, 'first second third', None)
42
Just keep in mind that "space" is not a typical delimiter for this use case.
Thanks for the accepted answer above. It was helpful.
In case anyone wants to extend the use for hasattr use the code below:
def rhasattr(obj, attr):
_nested_attrs = attr.split(".")
_curr_obj = obj
for _a in _nested_attrs[:-1]:
if hasattr(_curr_obj, _a):
_curr_obj = getattr(_curr_obj, _a)
else:
return False
return hasattr(_curr_obj, _nested_attrs[-1])
Ok so while typing the question I had an idea of how to do this and it seems to work fine. Here is what I came up with:
def set_attribute(obj, path_string, new_value):
parts = path_string.split('.')
final_attribute_index = len(parts)-1
current_attribute = obj
i = 0
for part in parts:
new_attr = getattr(current_attribute, part, None)
if current_attribute is None:
print 'Error %s not found in %s' % (part, current_attribute)
break
if i == final_attribute_index:
setattr(current_attribute, part, new_value)
current_attribute = new_attr
i+=1
def get_attribute(obj, path_string):
parts = path_string.split('.')
final_attribute_index = len(parts)-1
current_attribute = obj
i = 0
for part in parts:
new_attr = getattr(current_attribute, part, None)
if current_attribute is None:
print 'Error %s not found in %s' % (part, current_attribute)
return None
if i == final_attribute_index:
return getattr(current_attribute, part)
current_attribute = new_attr
i += 1
I guess this solves my question, but I am still curious if there is a better way to do this?
I feel like this has to be something pretty common in OOP and python, so I'm surprised gatattr and setattr do not support this natively.
Here's something similar to ChaimG's answer, but it works with an arbitrary number of cases. However, it only supports get attributes, not setting them.
requested_attr = 'pet.name'
parent = Person()
sub_names = requested_attr.split('.')
sub = None
for sub_name in sub_names:
try:
sub = parent.__getattribute__(sub_name)
parent = sub
except AttributeError:
raise Exception("The panel doesn't have an attribute that matches your request!")
pets_name = sub
I just love recursive functions
def rgetattr(obj,attr):
_this_func = rgetattr
sp = attr.split('.',1)
if len(sp)==1:
l,r = sp[0],''
else:
l,r = sp
obj = getattr(obj,l)
if r:
obj = _this_func(obj,r)
return obj
I know this post is pretty old but below code might help some one.
def getNestedObjectValue(obj={}, attr=""):
splittedFields = attr.split(".")
nestedValue = ""
previousValue = ""
for field in splittedFields:
previousValue = nestedValue
nestedValue = (
obj.get(field) if previousValue == "" else previousValue.get(field)
)
return nestedValue
print(
getNestedObjectValue(
obj={
"name": "ADASDASD",
"properties": {"somefield": {"value": "zxczxcxczxcxzc"}},
},
attr="properties.somefield.value",
)
)
Output
PS C:\myprograms\samples> python .\sample.py
zxczxcxczxcxzc
With regular python objects, it is easy to see their details.
E.g.
vec = (1, 2, 3)
print vec
=>
(1, 2, 3)
When working with equivalent OpenMaya (OM) objects, all print or str() shows is the type of object:
vec = OpenMaya.MFloatVector(1,2,3)
print vec
=>
<maya.OpenMaya.MFloatVector; proxy of <Swig Object of type 'MFloatVector *' at 0x000000002A346060> >
Is there a general way to ask an MObject to provide more details?
I'd like a result something like:
MFloatVector(1, 2, 3)
----- EDIT -----
From C++ documentation, I see that the information I want is available in C++ via the ostream << operator. E.g. for MFloatVector's << it says:
The format used is [x, y, z]
So another way to ask my question is: In python, how do I create an ostream in memory, send the object to it, then get the result as a string?
----- EDIT #2 -----
My import statement is:
import maya.OpenMaya as OpenMaya
This means I am using Version 1 of Maya Python API. (Because some of the Version 2 stuff is stubs, such as MGlobal. Examples I am looking at use these Version 1 features, so I stayed with Version 1.)
I've posted my own answer, which is to use Version 2, to get the desired behavior. TBD whether Version 2 has everything needed, and what is required to convert Version 1 examples to Version 2. For now, I'm sticking to Version 1. If anyone has a way to get Version 1 to provide more useful print details, that is what I would accept as an answer.
Version 2 of Maya Python API is more python-friendly.
To access Version 2, change the import statement from
import maya.OpenMaya as OpenMaya
to
import maya.api.OpenMaya as OpenMaya
This will require changes to the script, as many methods are tweaked to be more python friendly.
Once this is done, "print vec" and "str(vec)" and "len(vec)" all become useful operations:
vec = OpenMaya.MFloatVector(1, 2, 3)
print vec
print str(vec)
print len(vec)
=>
(1, 2, 3)
(1, 2, 3)
3
Well it is certainly possible to wrap the repr function with your own; something along the lines of:
import maya.OpenMaya as om
def repr_MfloatVector(self):
n = self.__class__.__name__
return "%s(%r, %r, %r)"%(n, self[0], self[1], self[2])
om.MFloatVector.__repr__ = repr_MfloatVector
vec = om.MFloatVector(1,2,3)
print vec
This would affect all future (but not past) MFloatVectors. But why you would go trough such a trouble is another matter. As for working in general for MObjects too much work on this level. However you could try to ask obj.length() of each object to determine if its iterable etc etc. so you could get a quite good spread still too much work for not much gain.
Here is code that defines "Repr(self)". A global function that returns "repr(self)" for most objects. But for objects that return a representation starting with "<", the result is a list of member values that (1) aren't internal (don't start with '__'), and (2) aren't methods.
E.g. an OpenMaya MFloatVector instance "OpenMaya.MFloatVector( 1, 2, 3)" gives result:
#MFloatVector( x: 1.0, y: 2.0, z: 3.0)
The code:
# ==================== AttributeAccess.py ====================
import maya.cmds as cmds
import maya.mel as mel
import sys
import maya.OpenMaya as OM # Version 1
import math
import inspect
import types
# ---------- Common Stuff ----------
# "something" can be any Python object.
def Exists(something):
return something is not None
def printElements(ob):
print '----- Elements: -----'
i = 0
for x in ob:
print ' [' + str(i) + ']: ' + repr(x)
i += 1
print '---------------------'
def printDictElements(ob):
print ''
print '-----------------------'
for x in ob: print repr(x) + ': ' + repr(ob[x])
print '-----------------------'
# ---------- inspect Attributes ----------
# NOTE: ob is an instance, NOT a type object.
def TypeName(ob):
return ob.__class__ .__name__
# Excludes 'internal' names (start with '__').
def Public(name):
return not name.startswith('__')
# member is element of inspect.getmembers:
# a two-element tuple.
def MemberWithType(member):
return ( member[0], TypeName(member[1]), member[1] )
#print MemberWithType( (1.1, 2) )
def Members(ob):
return inspect.getmembers(ob)
# True for Maya Python's 'this' member.
# member [1] is attribute value.
def SwigThis(member):
return (member[0] == 'this') and (TypeName(member[1]) == 'SwigPyObject')
# HACK: "not SwigThis": omit Maya Python's 'this' member.
def PublicMembers(ob):
members = filter(lambda member: Public(member[0]) and not SwigThis(member), Members(ob))
return map(MemberWithType, members)
# Excludes 'internal' names (start with '__').
def Dir(ob):
return filter(Public, dir(ob))
def _Type_And_Features(ob, names):
return '{0}.({1})'.format(TypeName(ob), ', '.join(names))
def MemberName(member):
return member[0]
# member with typename inserted as [1]. So descriptor is [2].
# member type-name is [1].
def CallableMember(member):
#return (member[2].__class__ is types.MethodType)
return inspect.isroutine(member[2])
def MemberNames(members):
return map(MemberName, members)
def Features(ob):
return _Type_And_Features(ob, MemberNames(PublicMembers(ob)) )
#return _Type_And_Features(ob, Dir(ob))
def Callable(ob):
return _Type_And_Features(ob, MemberNames(filter(lambda a: CallableMember(a), PublicMembers(ob))))
#return _Type_And_Features(ob, filter(lambda a: callable(a), Dir(ob)))
def IsClassVar(self, attrName):
return hasattr(self.__class__, attrName)
# REQUIRE attrName already known to be supported by self.
# But just in case, return False if exception, so will be skipped.
def IsNotSameAsClassVar(self, attrName):
try:
if not IsClassVar(self, attrName):
return True
# If it has different value than class' attribute, it is on the instance.
return getattr(self, attrName) is not getattr(self.__class__, attrName)
except:
return False
# ---------- _MayaValues ----------
# NOTE: 'ob' is an instance, not the class (type) itself.
def _ClassVars(ob):
attributes = filter(lambda a: not CallableMember(a), PublicMembers(ob))
# Keep class variables.
# "not IsProperty": HACK: Skip Maya/Swig 'property' class variables.
classVars = filter(lambda desc: IsClassVar(ob, desc[0]) and not IsProperty(getattr(ob.__class__, desc[0])), attributes)
return MemberNames(classVars)
# NOTE: 'ob' is an instance, not the class (type) itself.
def ClassVars(ob):
return _Header_And_Values(TypeName(ob) + ' Class_Variables',
map(lambda attr: attr + ': ' + Repr(getattr(ob, attr)), _ClassVars(ob)),
0
)
# If it is invocable without parameters, return (attrName, typename, result of invocation).
# if Not reportExceptions, return None for Exception.
def CallAttribute_AsTriple(self, attrName, reportExceptions=False):
try:
expressionString = 'self.{0}()'.format(attrName)
result = eval(expressionString)
typename = TypeName(result)
except Exception as e:
if reportExceptions:
result = e
typename = '*** Exception'
else:
return None
return (attrName, typename, result)
# member is tuple (attrName, typeName, value)
# If it is invocable without parameters, return (attrName, typename, result of invocation).
# if Not reportExceptions, return None for Exception.
def CallMember_AsTriple(self, member, reportExceptions=False):
return CallAttribute_AsTriple(self, member[0], reportExceptions)
# If it is invocable without parameters, return string: pretty-printed result of invocation.
# if Not reportExceptions, return None for Exception.
def CallAttribute(self, attrName, reportExceptions=False):
try:
#printElements(locals())
expressionString = 'self.{0}()'.format(attrName)
#print Eval(expressionString, locals())
result = eval(expressionString)
resultString = Repr(result)
typename = TypeName(result)
except Exception as e:
if reportExceptions:
#result = '*** Exception ' + str(e)
result = e
resultString = str(e)
typename = '*** Exception'
else:
return None
return ' .{0} {{{1}}}= {2}'.format(attrName, typename, resultString)
# member is tuple (attrName, typeName, value)
# If it is invocable without parameters, return string: pretty-printed result of invocation.
# if Not reportExceptions, return None for Exception.
def CallMemberRepr(self, member, reportExceptions=False):
return CallAttribute(self, member[0], reportExceptions)
def FirstLine(string):
lines = string.split('\n')
if len(lines) > 1:
return lines[0] + '...'
return string
def ArgLessRoutines_AsTriples(ob):
members = PublicMembers(ob)
members_WithNones = map(lambda member: CallMember_AsTriple(ob, member), members)
# member is tuple (attrName, typeName, value)
members = filter(Exists, members_WithNones)
return members
def ArgLessRoutines(ob):
members = PublicMembers(ob)
members_WithNones = map(lambda member: CallMember_AsTriple(ob, member), members)
# member is tuple (attrName, typeName, value)
members = filter(Exists, members_WithNones)
resultStrings = map(lambda string: FirstLine(string), resultStrings)
return _Header_And_Values(TypeName(ob) + ' ArgLessRoutines', resultStrings)
def _MayaCallables_Common(mayaType):
try:
typeName = mayaType.__name__
if typeName == 'MDagPath':
return ['fullPathName']
if typeName == 'MTypeId':
return ['id']
if typeName == 'MFnMesh':
return ['numPolygons', 'numVertices', 'numEdges', 'numFaceVertices']
if typeName == 'MDagPath':
return ['fullPathName']
except Exception as e:
print e
return []
def _MayaCallables_Version1(mayaType):
return _MayaCallables_Common(mayaType)
def _MayaCallables_Version2(mayaType):
return _MayaCallables_Common(mayaType)
# Names of callable attributes to include in Repr of 'ob'.
# For instances of types in 'maya.OpenMaya'.
def MayaCallables(ob):
try:
typ = ob.__class__
if typ == type:
return []
if typ.__module__ == 'maya.OpenMaya':
return _MayaCallables_Version1(typ)
if typ.__module__ == 'OpenMaya':
return _MayaCallables_Version2(typ)
except Exception as e:
print e
return []
# Return (name, typename, value) per maya callable.
def _MayaValues(ob):
callables = MayaCallables(ob)
members_WithNones = map(lambda attrName: CallAttribute_AsTriple(ob, attrName), callables)
members = filter(Exists, members_WithNones)
return members
# TODO: If all results fit on single line, remove "{typename}" so is more readable.
#def MayaValues(ob):
# resultStrings = _MayaValues(ob)
# return _Header_And_Values(TypeName(ob) + ' MayaValues', resultStrings)
# ---------- Attributes ----------
def _AttributeNames(ob):
attributes = filter(lambda a: not CallableMember(a), PublicMembers(ob))
# Omit class variables.
attributes = filter(lambda desc: IsNotSameAsClassVar(ob, desc[0]), attributes)
return MemberNames(attributes)
def AttributeNames(ob):
return _Type_And_Features(ob, _AttributeNames(ob))
#return _Type_And_Features(ob, filter(lambda a: not callable(a), Dir(ob)))
def _Header_And_Values(headerString, valueStrings, maxWidth=100):
if sum(map(len, valueStrings)) > maxWidth:
# pretty print, with one value per line.
return '{0}(\n {1}\n)'.format(headerString, '\n '.join(valueStrings))
return '{0}({1})'.format(headerString, ', '.join(valueStrings))
def _Type_And_Values(ob, valueStrings, maxWidth=100):
return _Header_And_Values(TypeName(ob), valueStrings, maxWidth)
def AttributeValues(ob):
return _Type_And_Values(ob, map(lambda attr: str(getattr(ob, attr)), _AttributeNames(ob)))
def Attributes(ob, depth=0):
# Limit recursion.
# If deep, don't include MayaValues.
if depth >= 2:
return _Type_And_Values(ob, map(lambda attr: attr + ': ' + str(getattr(ob, attr)), _AttributeNames(ob)))
attributes = map(lambda attr: attr + ': ' + Repr(getattr(ob, attr), depth + 1), _AttributeNames(ob))
if depth == 0:
mayaValues = _MayaValues(ob)
if len(mayaValues) > 0:
for mayaValue in mayaValues:
attribute = mayaValue[0] + ': ' + Repr(mayaValue[2])
attributes.append(attribute)
return _Type_And_Values(ob, attributes)
def IsProperty(ob):
return (TypeName(ob) == 'property')
# ---------- Repr ----------
def Repr(ob, depth=0):
r = repr(ob)
# Helps avoid undesired recursion.
if ob.__class__ == type:
return r
if (r.__class__ == types.StringType) and (len(r) > 0) and (r.find('<') <> 0):
# Has a good repr.
return r
# Doesn't have a good repr; inspect it instead.
return '#' + Attributes(ob, depth)
def Eval(expressionString, _locals=locals(), _globals=globals()):
return str(expressionString) + "= " + str(Repr(eval(expressionString, _globals, _locals)))
# ---------- Testing ----------
# ---------- class Vector ----------
class Vector(object):
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x, self.y, self.z = x, y, z
# Provide useful info for 'repr(self)', 'str(self)', and 'print self'.
def __repr__(self):
return 'Vector({0}, {1}, {2})'.format(self.x, self.y, self.z)
# math operators
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
# ==
def __eq__(self, other):
return (self.__class__ == other.__class__) and \
(self.x == other.x) and \
(self.y == other.y) and \
(self.z == other.z)
# a simple method
def ApproximateLength(self):
return self.x + self.y + self.z
# list/sequence/iterator support.
def tolist(self):
return [self.x, self.y, self.z]
def __len__(self):
return 3
# No need for "next(self)", because we create a list, use its iterator.
def __iter__(self):
return iter(self.tolist())
# class variable
Vector.Zero = Vector()
# ---------- inspecting Vector ----------
def Testing_Vector_Attributes():
#vec = (1, 2, 3)
#vec = [1, 2, 3]
#vec = Vector(1.0, 2.0, 3.0)
vec = OM.MFloatVector(1, 2, 3)
print vec
#for x in vec: print x
print dir(vec)
print TypeName(vec)
print Dir(vec)
print Features(vec)
print Callable(vec)
print '-----------------------'
printElements(PublicMembers(vec))
print '-----------------------'
print AttributeNames(vec)
#print vec.x
#print eval('vec.x')
#print getattr(vec, 'x')
print AttributeValues(vec)
print Attributes(vec)
vec = OM.MFloatVector(1, 2, 3)
#print repr(vec)
#print Repr('Hi')
print Repr( (1,2,3) )
print Repr(vec)
print ClassVars( Vector(1.0, 2.0, 3.0) )
print ClassVars( OM.MFloatVector(1, 2, 3) )
print Eval('OM.MMatrix()')
print Eval('OM.MMatrix().matrix')
if __name__ == "__main__":
Testing_Vector_Attributes()