How do I access an object's properties by name? - python

class a(type):
def __str__(self):
return 'aaa'
def __new__(cls, name, bases, attrs):
attrs['cool']='cool!!!!'
new_class = super(a,cls).__new__(cls, name, bases, attrs)
#if 'media' not in attrs:
#new_class.media ='media'
return new_class
class b(object):
__metaclass__=a
def __str__(self):
return 'bbb'
print b
print b()['cool']#how can i print 'cool!!!!'

print b().cool
attrs in your __new__ method becomes the object's dictionary. Properties of Python objects are referenced with the . syntax.

print "cool!!!"
Or did I miss something?

Related

Python can't use setter

here is my issue, when i'm using #property decorator I can't use setter
class Worker:
def __init__(self,name):
self.__name = name
#property
def name(self):
return self.__name
#name.setter
def set_name(self,new_name):
self.__name = new_name
worker1 = Worker('A')
print(worker1.name)
worker1.name = 'B'
print(worker1.name)
It gives AttributeError: can't set attribute 'name', when I use setter
Let's rewrite this without decorator syntax.
class Worker:
def __init__(self,name):
self.__name = name
def name(self):
return self.__name
name = proprety(name)
def set_name(self,new_name):
self.__name = new_name
set_name = name.setter(set_name)
This makes it easier to see that you now have two similar properties: name, which only provides read-only access to the __name attribute, and set_name, which has a getter and a setter for the same attribute. name.setter takes a method, and returns a new property that replaces the old setter (if any) of name with the given function.
You want a single property named name, so you must use the same name when defining the setter.
class Worker:
def __init__(self,name):
self.__name = name
#property
def name(self):
return self.__name
name = proprety(name)
#name.setter
def name(self,new_name):
self.__name = new_name
The decorators are a way to simplify code like the following:
class Worker:
def __init__(self,name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self,new_name):
self.__name = new_name
name = property(get_name, set_name)
del get_name, set_name
Change set_name to name.
Python docs for #property:
Be sure to give the additional functions the same name as the original property (x in this case.)

How to format strings in classes using attributes?

I want to format an attribute-string of a class with another attribute of the same class like this:
class Test:
def __init__(self):
self.name = None
self.full_name = 'name, {}'.format(self.name)
def print_name(self):
print(self.full_name)
my_object = Test()
my_object.name = 'my_object'
my_object.print_name()
Now it should print 'name, my_object'
But it prints 'name, None'
What to do that the string formats with the assigned value of the object?
You need to add full_name as a property so that you can add some more logic to it:
class Test:
def __init__(self):
self.name = None
#property
def full_name(self):
return f'name, {self.name}'
def print_name(self):
print(self.full_name)
my_object = Test()
my_object.name = 'my_object'
my_object.print_name()
Resources:
property function (built-in)

How to intercept class creation and add attribute using a metaclass?

In the following code, I want metaclass NameMeta to add attribute gender to MyName class in case this class does not declare that attribute.
class NameMeta(type):
def __new__(cls, name, bases, dic):
if 'gender' not in dic:
setattr(name, 'gender', 'Male')
return super().__new__(cls, name, bases, dic)
class MyName(metaclass=NameMeta):
def __init__(self, fname, lname):
self.fname = fname
self.lname = lname
def fullname(self):
self.full_name = self.fname + self.lname
return self.full_name
inst = MyName('Joseph ', 'Vincent')
print(MyName.gender)
This is the output that I am getting:
<ipython-input-111-550ff3cfae41> in __new__(cls, name, bases, dic)
2 def __new__(cls, name, bases, dic):
3 if 'gender' not in dic:
----> 4 setattr(name, 'gender', 'Male')
5 return super().__new__(cls, name, bases, dic)
6
AttributeError: 'str' object has no attribute 'gender'
I know this error makes sense since name is a string.
My question is, how can I access MyName class as an object in the metaclass so that I can add the attribute?
You were close. Your problem is that you were trying to add your attribute to the name of the meta-class using name, which is a string. You need to assign the attribute to the class object you're creating. This can be done using dic:
class NameMeta(type):
def __new__(cls, name, bases, dic):
if 'gender' not in dic:
dic['gender'] = 'Male'
return super().__new__(cls, name, bases, dic)
With the above change your code outputs:
Male
You can just add it to the dic if it is not present, as it holds the class's attribute:
def __new__(mcs, name, bases, dict):
if 'gender' not in dict:
dict['gender'] = 'Male'
# or just `dict.setdefault('gender', 'Male')`
return super().__new__(mcs, name, bases, dic)
# Or you can create the class and set it
cls = super().__new__(mcs, name, bases, dic)
if not hasattr(cls, 'gender'):
cls.gender = 'Male'
return cls
Or you could have a class attribute:
class NameMeta(type):
gender = 'Male'
# `gender = 'Male'` will be inherited by all classes
# but not instances of those classes.

How to create abstract properties in python abstract classes?

In the following code, I create a base abstract class Base. I want all the classes that inherit from Base to provide the name property, so I made this property an #abstractmethod.
Then I created a subclass of Base, called Base_1, which is meant to supply some functionality, but still remain abstract. There is no name property in Base_1, but nevertheless python instatinates an object of that class without an error. How does one create abstract properties?
from abc import ABCMeta, abstractmethod
class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
__metaclass__ = ABCMeta
def __init__(self, str_dir_config):
self.str_dir_config = str_dir_config
#abstractmethod
def _do_stuff(self, signals):
pass
#property
#abstractmethod
def name(self):
"""This property will be supplied by the inheriting classes
individually.
"""
pass
class Base1(Base):
__metaclass__ = ABCMeta
"""This class does not provide the name property and should
raise an error.
"""
def __init__(self, str_dir_config):
super(Base1, self).__init__(str_dir_config)
# super().__init__(str_dir_config) <- Python 3
def _do_stuff(self, signals):
print "Base_1 does stuff"
# print("Base_1 does stuff") <- Python 3
class C(Base1):
#property
def name(self):
return "class C"
if __name__ == "__main__":
b1 = Base1("abc")
Since Python 3.3 a bug was fixed meaning the property() decorator is now correctly identified as abstract when applied to an abstract method.
Note: Order matters, you have to use #property above #abstractmethod
Python 3.3+: (python docs):
from abc import ABC, abstractmethod
class C(ABC):
#property
#abstractmethod
def my_abstract_property(self):
...
Python 2: (python docs)
from abc import ABC, abstractproperty
class C(ABC):
#abstractproperty
def my_abstract_property(self):
...
Until Python 3.3, you cannot nest #abstractmethod and #property.
Use #abstractproperty to create abstract properties (docs).
from abc import ABCMeta, abstractmethod, abstractproperty
class Base(object):
# ...
#abstractproperty
def name(self):
pass
The code now raises the correct exception:
Traceback (most recent call last):
File "foo.py", line 36, in
b1 = Base_1('abc')
TypeError: Can't instantiate abstract class Base_1 with abstract methods name
Based on James answer above
def compatibleabstractproperty(func):
if sys.version_info > (3, 3):
return property(abstractmethod(func))
else:
return abstractproperty(func)
and use it as a decorator
#compatibleabstractproperty
def env(self):
raise NotImplementedError()
In python 3.6+, you can also anotate a variable without providing a default. I find this to be a more concise way to make it abstract.
class Base():
name: str
def print_name(self):
print(self.name) # will raise an Attribute error at runtime if `name` isn't defined in subclass
class Base_1(Base):
name = "base one"
it may also be used to force you to initialize the variable in the __new__ or __init__ methods
As another example, the following code will fail when you try to initialize the Base_1 class
class Base():
name: str
def __init__(self):
self.print_name()
class Base_1(Base):
_nemo = "base one"
b = Base_1()
AttributeError: 'Base_1' object has no attribute 'name'
Using the #property decorator in the abstract class (as recommended in the answer by James) works if you want the required instance level attributes to use the property decorator as well.
If you don't want to use the property decorator, you can use super(). I ended up using something like the __post_init__() from dataclasses and it gets the desired functionality for instance level attributes:
import abc
from typing import List
class Abstract(abc.ABC):
"""An ABC with required attributes.
Attributes:
attr0
attr1
"""
#abc.abstractmethod
def __init__(self):
"""Forces you to implement __init__ in 'Concrete'.
Make sure to call __post_init__() from inside 'Concrete'."""
def __post_init__(self):
self._has_required_attributes()
# You can also type check here if you want.
def _has_required_attributes(self):
req_attrs: List[str] = ['attr0', 'attr1']
for attr in req_attrs:
if not hasattr(self, attr):
raise AttributeError(f"Missing attribute: '{attr}'")
class Concrete(Abstract):
def __init__(self, attr0, attr1):
self.attr0 = attr0
self.attr1 = attr1
self.attr2 = "some value" # not required
super().__post_init__() # Enforces the attribute requirement.
For example, you can define the abstract getter, setter and deleter with #abstractmethod and #property, #name.setter or #name.deleter in Person abstract class as shown below. *#abstractmethod must be the innermost decorator otherwise error occurs:
from abc import ABC, abstractmethod
class Person(ABC):
#property
#abstractmethod # The innermost decorator
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
Then, you can extend Person abstract class with Student class, override the abstract getter, setter and deleter in Student class, instantiate Student class and call the getter, setter and deleter as shown below:
class Student(Person):
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides abstract getter
return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))
Output:
John
Tom
False
Actually, even if you don't override the abstract setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides only abstract getter
return self._name
# #name.setter
# def name(self, name): # Overrides abstract setter
# self._name = name
# #name.deleter
# def name(self): # Overrides abstract deleter
# del self._name
obj = Student("John") # Instantiates "Student" class
# ...
No error occurs as shown below:
John
Tom
False
But, if you don't override the abstract getter, setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only abstract getter
# return self._name
# #name.setter
# def name(self, name): # Overrides abstract setter
# self._name = name
# #name.deleter
# def name(self): # Overrides abstract deleter
# del self._name
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
TypeError: Can't instantiate abstract class Student with abstract methods name
And, if you don't override the abstract getter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only abstract getter
# return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
NameError: name 'name' is not defined
And, if #abstractmethod is not the innermost decorator as shown below:
from abc import ABC, abstractmethod
class Person(ABC):
#abstractmethod # Not the innermost decorator
#property
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
The error below occurs:
AttributeError: attribute 'isabstractmethod' of 'property' objects is not writable
Another possible solution is to use metaclasses.
A minimal example can look like this:
class BaseMetaClass(type):
def __new__(mcls, class_name, bases, attrs):
required_attrs = ('foo', 'bar')
for attr in required_attrs:
if not attr in attrs:
raise RunTimeError(f"You need to set {attr} in {class_name}")
return super().__new__(mcls, class_name, bases, attrs)
class Base(metaclass=BaseMeta):
foo: str
bar: int
One advantage of this approach is that the check will happen at definition time (not instantiation).
Also, setting class attributes in child classes is a bit easier than declaring properties (as long as they are simple values known in advance) and your final classes will look more concise

Dealing with metaclass conflict with SQL Alchemy declarative base

I have a class X which derives from a class with its own metaclass Meta. I want to also derive X from the declarative base in SQL Alchemy. But I can't do the simple
def class MyBase(metaclass = Meta):
#...
def class X(declarative_base(), MyBase):
#...
since I would get metaclass conflict error: 'the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases'. I understand that I need to create a new metaclass that would derive from both Meta and from whatever metaclass the declarative base uses (DeclarativeMeta I think?). So is it enough to write:
def class NewMeta(Meta, DeclarativeMeta): pass
def class MyBase(metaclass = NewMeta):
#...
def class X(declarative_base(), MyBase):
#...
I tried this, and it seems to work; but I'm afraid I may have introduced some problem with this code.
I read the manual, but it's a bit too cryptic for me. What's
EDIT:
The code used for my classes is as follows:
class IterRegistry(type):
def __new__(cls, name, bases, attr):
attr['_registry'] = {}
attr['_frozen'] = False
print(name, bases)
print(type(cls))
return type.__new__(cls, name, bases, attr)
def __iter__(cls):
return iter(cls._registry.values())
class SQLEnumMeta(IterRegistry, DeclarativeMeta): pass
class EnumType(metaclass = IterRegistry):
def __init__(self, token):
if hasattr(self, 'token'):
return
self.token = token
self.id = len(type(self)._registry)
type(self)._registry[token] = self
def __new__(cls, token):
if token in cls._registry:
return cls._registry[token]
else:
if cls._frozen:
raise TypeError('No more instances allowed')
else:
return object.__new__(cls)
#classmethod
def freeze(cls):
cls._frozen = True
def __repr__(self):
return self.token
#classmethod
def instance(cls, token):
return cls._registry[token]
class C1(Base, EnumType, metaclass = SQLEnumMeta):
__tablename__ = 'c1'
#...
Edit: Now having looked at IterRegistry and DeclarativeMeta, I think you're code is okay.
IterRegistry defines __new__ and __iter__, while DeclarativeMeta defines __init__ and __setattr__. Since there is no overlap, there's no direct need to call super. Nevertheless, it would good to do so, to future-proof your code.
Do you have control over the definition of Meta? Can you show us its definition? I don't think we can say it works or does not work unless we see the definition of Meta.
For example, there is a potential problem if your Meta does not call
super(Meta,cls).__init__(classname, bases, dict_)
If you run this code
class DeclarativeMeta(type):
def __init__(cls, classname, bases, dict_):
print('DeclarativeMeta')
# if '_decl_class_registry' in cls.__dict__:
# return type.__init__(cls, classname, bases, dict_)
# _as_declarative(cls, classname, dict_)
return type.__init__(cls, classname, bases, dict_)
class Meta(type):
def __init__(cls, classname, bases, dict_):
print('Meta')
return type.__init__(cls, classname, bases, dict_)
class NewMeta(Meta,DeclarativeMeta): pass
class MyBase(object):
__metaclass__ = NewMeta
pass
Then only the string 'Meta' gets printed.
In other words, only Meta.__init__ gets run. DeclarativeMeta.__init__ gets skipped.
On the other hand, if you define
class Meta(type):
def __init__(cls, classname, bases, dict_):
print('Meta')
return super(Meta,cls).__init__(classname, bases, dict_)
Then both Meta.__init__ and DeclarativeMeta.__init__ gets run.

Categories